はじめに
JavaScript では、スコープ、コンテキスト、クロージャー、関数などが最高のものです。初心者の JSers にとっては、上級レベルの必需品です。フロントエンドのシージエンジニアは、落ち着いてこれらの本質を理解することによってのみ、エレガントなコードを書くことができます。
この記事は、忘れがちな重要な知識を要約することを目的としており、基本的な概念については説明しません。基本的な知識がよくわからない場合は、「JavaScript の決定版ガイド」を読んでください~
言語特徴関数式
まずコード スニペットを見てください:
ここで言いたいのは、関数式内の foo は関数内でのみ参照でき、関数外では参照できないということです。
json
多くの JavaScript 開発者は、JavaScript オブジェクト リテラル (JSON オブジェクト) を誤って呼んでいます。 JSON はデータ交換形式を記述するように設計されており、JavaScript のサブセットである独自の構文も備えています。
{ “prop”: “val” } このような宣言は、使用されるコンテキストに応じて、JavaScript オブジェクト リテラルまたは JSON 文字列の場合があります。文字列コンテキスト (一重引用符または二重引用符で囲まれるか、テキスト ファイルから読み取られる) で使用される場合、それは JSON 文字列です。オブジェクト リテラル コンテキストで使用される場合、それはオブジェクト リテラルです。
もう 1 つ知っておくべきことは、JSON.parse は JSON 文字列をオブジェクトに逆シリアル化するために使用され、JSON.stringify はオブジェクトを JSON 文字列にシリアル化するために使用されるということです。古いバージョンのブラウザはこのオブジェクトをサポートしていませんが、json2.js を通じて同じ機能を実現できます。
プロトタイプ
(新しいオブジェクトを割り当てることによって) 関数のプロトタイプ プロパティを完全に変更すると、作成するオブジェクトにはコンストラクター プロパティが含まれないため、元のコンストラクターへの参照が失われます。
MDN のコンストラクターの説明を見てみましょう。 プロトタイプ: インスタンスのプロトタイプを作成したオブジェクト関数への参照を返します。 したがって、関数へのプロトタイプ参照は手動で復元する必要があります。
ただし、プロトタイプ属性を送信しても、すでに作成されているオブジェクトのプロトタイプには影響しません (コンストラクターのプロトタイプ属性が変更された場合にのみ影響を受けます)。つまり、新しく作成されたオブジェクトのみが新しいプロトタイプを持ちます。すでに作成されたオブジェクトには、元の古いプロトタイプへの参照が引き続き残ります (このプロトタイプは変更できなくなります)。
変数オブジェクト 関数実行コンテキストでは、VO(変数オブジェクト)に直接アクセスすることはできません。このとき、活性化オブジェクト(活性化オブジェクト)がVOの役割を果たします。 アクティブ オブジェクトは、関数コンテキストに入るときに作成され、関数の argument 属性を通じて初期化されます。 argument 属性の値は Arguments オブジェクトです:
•すべての関数宣言 (FunctionDeclaration、FD);
•すべての変数宣言 (var、VariableDeclaration)
もう 1 つの典型的な例:
コードをコピー
コードをコピー
この例では、関数オブジェクトはありますが、参照型オブジェクトはありません (識別子でもプロパティ アクセサーでもありません)。したがって、 this 値は最終的にグローバル オブジェクトに設定されます。
問題は、次の 3 つの呼び出しでは、特定の操作を適用した後、呼び出し括弧の左側の値が参照型ではなくなることです。
•最初の例は明らかです - 明らかな参照型です。結果として、これが基本オブジェクト、つまり foo になります。
• 2 番目の例では、グループ演算子は適用されません。GetValue など、参照型からオブジェクトの実際の値を取得する上記のメソッドを考えてください。同様に、グループ操作の戻りでも参照型を取得します。これが、 this 値がベース オブジェクト (foo) に再度設定される理由です。
•3 番目の例では、グループ演算子とは異なり、代入演算子は GetValue メソッドを呼び出します。返される結果は関数オブジェクト (参照型ではありません) です。つまり、これは null に設定され、結果はグローバル オブジェクトになります。
• 4 番目と 5 番目も同様です。コンマ演算子と論理演算子 (OR) が GetValue メソッドを呼び出し、それに応じて参照が失われ、関数が取得されます。そして、再度グローバルに設定します。
ご存知のとおり、ローカル変数、内部関数、仮パラメータは、指定された関数のアクティベーション オブジェクトに格納されます。
アクティブなオブジェクトは常に this として返され、値は null です (つまり、疑似コードの AO.bar() は null.bar() と同等です)。ここで、これをグローバル オブジェクトに設定して、上で説明した例に戻ります。
スコープチェーン
関数コンストラクターを通じて作成された関数のスコープ属性は、常に唯一のグローバル オブジェクトです。
重要な例外の 1 つは、関数コンストラクターを介して作成された関数に関するものです。
また:
コンテキストを入力するとどうなりますか?識別子「x」と「y」が変数オブジェクトに追加されました。さらに、コードの実行フェーズ中に次の変更を加えます:
•x = 10, y = 10;
•オブジェクト {x:20} がスコープの先頭に追加されます;
•with 内で var 宣言が検出されますが、もちろん何もありませんコンテキストに入るときに、すべての変数が解析されて追加されているため、 が作成されます。
• 2 番目のステップでは、変数 "x" のみが変更され、実際にはオブジェクト内の "x" が解析されて追加されます。スコープチェーンの先頭、「x」は 20 で 30 になります;
• 変数オブジェクト「y」も解析された後、値が 10 から 30 に変わります;
•さらに、with ステートメントが完了すると、その特定のオブジェクトがスコープ チェーンから削除されます (変更された変数 "x" - 30 もそのオブジェクトから削除されます)。つまり、スコープ チェーンの構造が状態に復元されます。 withが強化される前。
•最後の 2 つのアラートでは、現在の変数オブジェクトの "x" は同じままですが、"y" の値は 30 に等しくなりますが、これは with ステートメントの実行中に変更されました。
関数
括弧に関する質問
質問を見てみましょう: 「関数を作成した直後に呼び出す場合、なぜ関数を括弧で囲む必要があるのですか?」 』の答えは、表現文の制限はこうです。
標準によれば、式ステートメントはコード ブロックと区別することが難しいため、中括弧 { で始めることはできません。同様に、関数宣言と区別することが難しいため、関数キーワードで始めることもできません。つまり、作成直後にすぐに実行する関数を定義する場合は、次のように呼び出します:
上記 2 つの定義では関数宣言を使用しましたが、インタープリターは解釈時にエラーを報告しますが、多くの理由が考えられます。これがグローバル コード (つまり、プログラム レベル) で定義されている場合、関数キーワードで始まるため、インタープリターはそれを関数宣言として扱います。最初の例では、関数宣言に名前がないため、SyntaxError が発生します。 (関数宣言には名前が必要であると前述しました)。 2 番目の例では、foo という名前の関数宣言が正常に作成されていますが、それでも構文エラー、つまり式のないグループ化演算子エラーが発生します。これは実際には、関数呼び出しで使用される括弧ではなく、関数宣言後のグループ化演算子です。したがって、次のコードを宣言するとします:
式を作成する最も簡単な方法は、グループ化演算子の括弧を使用することです。そのため、インタープリタがそれを解釈するときにあいまいさがなくなります。この関数はコード実行フェーズ中に作成され、すぐに実行され、その後自動的に破棄されます (参照がない場合)
上記のコードは、式を括弧で囲み、(1) を通じて呼び出すものです。すぐに実行される次の関数の場合、関数を囲む括弧は必要ないことに注意してください。関数はすでに式の位置にあり、関数の実行フェーズで作成される必要がある FE を処理していることがパーサーに認識されているためです。 、関数が作成された直後に呼び出されます。
ご覧のとおり、foo.bar は文字列であり関数ではありません。この関数は条件パラメータに基づいてこのプロパティを初期化するためにのみ使用され、作成されてすぐに呼び出されます。
1. したがって、「括弧について」という質問に対する完全な答えは次のとおりです。
2. 関数が式の位置にない場合、グループ化演算子の括弧が必要です (つまり、手動で)。関数はFEに変換されます。
3. パーサーが FE を扱っていることを認識している場合は、括弧を使用する必要はありません。
自由変数:
クロージャーの静的スコープ:
理論: スコープ チェーンのため、すべての関数はクロージャです (関数の種類に関係なく、匿名関数、FE、NFE、FD はすべてクロージャです)。実用的な観点から: 以下の関数はクロージャとみなされます: * 作成されたコンテキストが破棄された場合でも、依然として存在します (たとえば、内部関数が親関数から返される)
* コード内で自由変数
を参照します最後に:
ECMAScript は、プロトタイプベースの委任継承をサポートするオブジェクト指向言語です。