JavaScriptを深く理解するシリーズ(10) JavaScriptコア(上級者必読)_javascriptスキル

WBOY
リリース: 2016-05-16 17:56:59
オリジナル
989 人が閲覧しました

適切な読者: 経験豊富な開発者、プロのフロントエンド担当者。

原著者: Dmitry A. Soshnikov
公開時期: 2010-09-02
原文: http://dmitrysoshnikov.com/ecmascript/javascript-the-core/
参考文献1 : http://ued.ctrip.com/blog/?p=2795
参考 2: http://www.cnblogs.com/ifishing/archive/2010/12/08/1900594.html
Main 上記 2 人の専門家の中国語訳を組み合わせ、2 つの記事の良い部分を組み合わせたものです。

まず、ECMASript の最も基本的な概念でもあるオブジェクト [Object] の概念を見てみましょう。

Object オブジェクト
ECMAScript は高度に抽象化されたオブジェクト指向言語であり、Object オブジェクトを処理するために使用されます。もちろん、基本的な型もありますが、必要に応じてオブジェクト オブジェクトに変換する必要もあります。 。 使用。

オブジェクトはプロパティのコレクションであり、プロトタイプ オブジェクトはオブジェクトまたは null 値のいずれかです。このプロトタイプ オブジェクト [プロトタイプ オブジェクト] は、オブジェクトまたは null 値。
コードをコピー
基本的なオブジェクトの例を見てみましょう。まず、オブジェクトのプロトタイプが内部 [[prototype]] 属性への参照であることを理解する必要があります。

しかし、一般的に言えば、__proto__ のような二重括弧の代わりに __<内部属性名>__ アンダースコアを使用します (これは、SpiderMonkey などの一部のスクリプト エンジンのプロトタイプ概念の特定の実装ですが、標準ではありません) )。


var foo = {
x: 10,
y: 20
};


上記のコード foo オブジェクトには、2 つの明示的なプロパティ [明示的な独自プロパティ] と、暗黙的な __proto__ プロパティ [暗黙的な __proto__ プロパティ] があり、 foo のプロトタイプ。


図 1. プロトタイプを備えた基本オブジェクト

なぜプロトタイプが必要なのでしょうか? この質問に答えるために、プロトタイプ チェーンの概念を考えてみましょう。

プロトタイプ チェーン
プロトタイプ オブジェクトも通常のオブジェクトであり、プロトタイプ オブジェクトのプロトタイプが null でない場合、それをプロトタイプ チェーンと呼びます。 )。
プロトタイプ チェーンは、継承と共有プロパティの実装に使用されるオブジェクトの有限チェーンです。

次のような状況を想像してください。2 つのオブジェクトがあり、コンテンツの大部分は同じで、ほんの一部だけが異なります。明らかに、適切なデザイン パターンでは、同じ部分を再利用する必要があります。すべてのオブジェクトで同じメソッドやプロパティを繰り返し定義しないでください。クラスベースのシステムでは、これらの再利用された部分はクラスの継承と呼ばれます。同じ部分がクラス A に置かれ、次にクラス B とクラス C が A から継承し、それぞれに固有のものを要求できます。

ECMAScript にはクラスの概念がありません。ただし、再利用の概念は同じであり (ある点ではクラスよりも柔軟です)、プロトタイプ チェーンによって実装できます。この種の継承は、委任ベースの継承、またはより一般的にはプロトタイプ継承と呼ばれます。

クラス "A"、"B"、"C" と同様に、ECMAScript ではオブジェクト クラス "a"、"b"、"c" を作成します。したがって、オブジェクト "a" はオブジェクト " を所有します。 b」と「c」の一部。同時に、オブジェクト "b" と "c" には、独自の追加プロパティまたはメソッドのみが含まれます。



var a = { x: 10、calculate : function (z) { return this.x this.y z }} var b = { y: 20, __proto__: a}; // 継承された関数を呼び出します。メソッド b .calculate(30); // 60c.calculate(40); // 80


これはとても簡単だと思いませんか? b と c は、a で定義された計算メソッドを使用できます。これは、プロトタイプ チェーン [プロトタイプ チェーン] によって実装されます。

原理は非常に単純です。オブジェクト b で計算メソッドが見つからない場合 (つまり、オブジェクト b に計算属性がない場合)、プロトタイプ チェーンに沿って検索が開始されます。この計算メソッドが b のプロトタイプで見つからない場合、a のプロトタイプがプロトタイプ チェーンに沿って見つかり、プロトタイプ チェーン全体が走査されます。見つかった場合は、最初に見つかったプロパティまたはメソッドが返されることに注意してください。したがって、最初に見つかったプロパティが継承プロパティになります。プロトタイプ チェーン全体を走査しても見つからない場合は、未定義が返されます。

継承メカニズムでは、this の値は、プロトタイプ チェーンから見つかったときに属しているオブジェクトではなく、元々属していたオブジェクトを依然として指すことに注意してください。たとえば、上記の例では、this.y は a ではなく b と c から取得されます。もちろん、this.x はプロトタイプ チェーン メカニズムを通じて検出されるため、a から取得されたものであることもわかりました。

オブジェクトのプロトタイプが明示的に宣言または定義されていない場合、__prototype__ のデフォルト値は object.prototype であり、object.prototype にはプロトタイプ チェーンの終わりである __prototype__ も含まれます。 、null に設定されます。

上記の a, b, c の継承関係を下図に示します

図 2. プロトタイプ チェーン

プロトタイプ チェーンは通常、オブジェクトが同じまたは類似の状態構造 (つまり、同じプロパティのセット) と異なる状態値を持つ状況で使用されます。この場合、Constructor を使用して、指定されたパターンでオブジェクトを作成できます。
コンストラクター
コンストラクターは、オブジェクトの作成に加えて、作成された新しいオブジェクトのプロトタイプ オブジェクトを自動的に設定するという別の便利な機能も実行します。プロトタイプ オブジェクトは ConstructorFunction.prototype プロパティに保存されます。

たとえば、前の例を書き直し、コンストラクターを使用してオブジェクト "b" と "c" を作成すると、オブジェクト "a" は "Foo.prototype" の役割を果たします。

コードをコピー コードは次のとおりです:

//Constructor
function Foo(y) {
//構築 この関数は特定のモードでオブジェクトを作成します: 作成されたオブジェクトは "y" 属性を持ちます
this.y = y;
}

// "Foo.prototype"新しいオブジェクトのプロトタイプ参照を保存します
// したがって、それを使用して継承と共有プロパティまたはメソッドを定義できます
// したがって、上記の例と同様に、次のコードがあります:

//継承プロパティ" x"
Foo.prototype.x = 10;

// 継承メソッド "calculate"
Foo.prototype.calculate = function (z) {
return this.x this.y z ;
};

// foo パターンを使用して "b" と "c" を作成します
var b = new Foo(20);
var c = new Foo(30) );

// 継承されたメソッドを呼び出します
b.calculate(30); // 60
c.calculate(40); // 見てみましょう予期されるプロパティを使用しました

console.log(

b.__proto__ === Foo.prototype, // true
c.__proto__ === Foo.prototype, // true

// "Foo.prototype" は特別な属性 "constructor" を自動的に作成します
// a 自体のコンストラクターを指します
// インスタンス "b" と "c" は Find it を通じて承認できますそれを使用して独自のコンストラクターをテストします。

b.constructor === Foo, // true
c.constructor === Foo, // true
Foo.prototype.constructor == = Foo // true

b.calculate === b.__proto__.calculate, // true
b.__proto__.calculate === Foo.prototype.calculate // true

);


上記のコードは次の関係として表現できます:

図 3. コンストラクターとオブジェクトの関係

上の図からわかるように、各オブジェクトにはプロトタイプがあり、コンストラクター Foo には独自の __proto__ (Function.prototype) があり、Function.prototype の __proto__ は Object.prototype を指します。 Foo.prototype は単なる明示的な属性、つまり b と c の __proto__ 属性です。
この問題の完全かつ詳細な説明は、叔父が間もなく翻訳する第 18 章と第 19 章に記載されています。オブジェクト指向プログラミング (OOP。一般理論) では、さまざまなオブジェクト指向のパラダイムとスタイル (OOP のパラダイムとスタイル) と、ECMAScript の実装との比較を説明します。 OOP) 。ECMAScript 実装)、特に ECMAScript でのオブジェクト指向プログラミングについて説明します。

オブジェクトの基本原理を理解したところで、ECMAScript のプログラム実行環境 [ランタイム プログラム実行] を見てみましょう。これは一般に「実行コンテキスト スタック」[実行コンテキスト] スタックと呼ばれます。各要素はオブジェクトとして抽象的に理解できます。 ECMAScript では、ほとんどどこにでもオブジェクトが表示されることに気づいたかもしれません。

実行コンテキスト スタック
ECMASscript には、global、function、eval の 3 種類のコードがあります。

各タイプのコードの実行は、それぞれのコンテキストに依存します。もちろん、グローバル コンテキストは多くの関数および評価インスタンスをカバーする可能性があります。関数が呼び出されるたびに、関数実行のコンテキストに入り、関数内の変数の値が計算されます。 eval 関数を実行するたびに、eval 実行のコンテキストにも入り、変数の値をどこで取得するかを決定します。

関数呼び出し (再帰でも) は新しいコンテキストを生成するため、関数は無制限のコンテキストを生成する可能性があることに注意してください。

コードをコピー コードは次のとおりです。

function foo(bar) {}
// 同じ関数を呼び出すと、毎回 3 つの異なるコンテキストが生成されます
//(パラメーター bar の値などの異なる状態を含む)

foo(10); ;
foo(30);
関数が別の関数を呼び出し (またはグローバル コンテキストがグローバル関数を呼び出し)、それをレイヤーごとに呼び出すのと同じように、実行コンテキストは別のコンテキストをアクティブ化できます。論理的に言えば、この実装はスタックであり、コンテキスト スタックと呼ぶことができます。

他のコンテキストを起動するコンテキストを呼び出し元と呼びます。アクティブ化されたコンテキストは呼び出し先と呼ばれます。呼び出し先が呼び出し元である場合もあります (たとえば、グローバル コンテキストで呼び出される関数は、独自の内部メソッドの一部を呼び出します)。

呼び出し元が呼び出し先をアクティブ化すると、呼び出し元は自身の実行を一時停止し、呼び出し先に制御を移します。これは、実行コンテキスト [実行コンテキスト] /アクティブ実行と呼ばれます。この呼び出し先のコンテキストが終了すると、制御は再び呼び出し元に渡され、呼び出し元は一時停止された場所から実行を継続します。この呼び出し元が終了した後も、他の​​コンテキストは引き続きトリガーされます。呼び出し先は、例外を返すかスローすることで、自身のコンテキストを終了できます。

以下に示すように、すべての ECMAScript プログラムの実行は、実行コンテキスト スタック [実行コンテキスト (EC) スタック] と見なされます。スタックの最上位はアクティブなコンテキストです。

図 4. 実行コンテキスト スタック

プログラムが開始されると、まずグローバル実行コンテキスト [グローバル実行コンテキスト] に入ります。これはスタックの最下位要素でもあります。このグローバル プログラムは、このグローバル コンテキストの実行中に、必要なオブジェクトと関数の初期化と生成を開始し、いくつかのメソッド (もちろんすでに初期化されています) をアクティブ化し、そのコンテキストに新しい要素をプッシュします。これらの初期化が完了すると、システムはいくつかのイベント (ユーザーのマウス クリックなど) を待機し、いくつかのメソッドをトリガーして、新しいコンテキストに入ります。

図 5 を参照してください。関数コンテキスト「EC1」とグローバル コンテキスト「Global EC」があります。次の図は、「Global EC」から「EC1」に入るときと出るときのスタックの変化を示しています。

図 5. 実行コンテキスト スタックの変更

これは、ECMAScript ランタイム システムがコードの実行を管理する方法です。

ECMAScript 実行コンテキスト スタックの詳細については、この一連のチュートリアルの

第 11 章 実行コンテキスト (実行コンテキスト) を参照してください。

上で述べたように、スタック内の各実行コンテキストはオブジェクトとして表すことができます。コンテキスト オブジェクトの構造と、そのコードを実行するために必要な状態を見てみましょう。


実行コンテキスト
実行コンテキストは、オブジェクトとして抽象的に理解できます。各実行コンテキストには、関連するコードの実行の進行状況を追跡するために使用される一連のプロパティ (コンテキスト状態と呼ばれます) があります。この図はコンテキストの構造です。

図 6. コンテキスト構造


これら 3 つの必須属性 (変数オブジェクト (変数オブジェクト)、このポインター (この値)、スコープ チェーン (スコープ チェーン)) に加えて、実行コンテキストは特定の実装には追加のプロパティを持つこともできます。次に、これら 3 つのプロパティを詳しく見てみましょう。

変数オブジェクト
変数オブジェクトは、実行コンテキストに関連するデータのスコープです。
コンテキスト内で定義されている変数と関数宣言を格納する、コンテキストに関連付けられた特別なオブジェクトです。

変数オブジェクトは、実行コンテキストに関連するデータのスコープです。
これはコンテキストに関連付けられた特別なオブジェクトであり、コンテキストで定義された変数と関数宣言を保存するために使用されます。
コードをコピー
注: 関数式 [関数式] (関数宣言ではなく、違いについてはこのシリーズの第 2 章を参照してください) は、VO[変数オブジェクト] には含まれません。

変数オブジェクトは、さまざまなコンテキストでのさまざまなオブジェクトの使用を表す抽象的な概念です。たとえば、グローバル コンテキストでは、変数オブジェクトはグローバル オブジェクトそのものでもあります。 (これが、グローバル オブジェクトのプロパティを通じてグローバル変数を指すことができる理由です)。

次の例でグローバル実行コンテキストを見てみましょう:

コードをコピーします コードは次のとおりです。
var foo = 10;

function bar() {} // // 関数宣言
(function baz() {}); expression

console.log(
this.foo == foo, // true
window.bar == bar // true
); (baz); // 参照エラー、baz が定義されていません


グローバル コンテキストの変数オブジェクト (VO) には次の属性があります:

図 7. グローバル変数オブジェクト

上に示したように、関数「baz」を関数式として使用した場合、変数オブジェクトには含まれません。これが、関数の外でアクセスしようとすると ReferenceError が発生する理由です。 ECMAScript では、他の言語 (C/C++ など) と比較して、関数のみが新しいスコープを作成できることに注意してください。関数内で定義された変数と内部関数は外部からは直接見えず、グローバル オブジェクトを汚染しません。 eval を使用する場合は、新しい (eval で作成された) 実行コンテキストも使用します。 eval は、グローバル変数オブジェクトまたは呼び出し元の変数オブジェクト (eval の呼び出しのソース) を使用します。

関数とその独自の変数オブジェクトはどうなるでしょうか? 関数のコンテキストでは、変数オブジェクトはアクティベーション オブジェクトとして表されます。
アクティブ化オブジェクト
呼び出し元によって関数がアクティブ化されると、この特別なアクティブ化オブジェクトが作成されます。これには、通常のパラメータ (仮パラメータ) と特殊パラメータ (引数) オブジェクト (インデックス付き属性を持つパラメータ マッピング テーブル) が含まれます。アクティブ オブジェクトは、関数コンテキストで変数オブジェクトとして使用されます。

つまり、関数の変数オブジェクトは変更されませんが、格納変数と関数宣言に加えて、特別なオブジェクト引数も含まれます。

次の状況を考えてみましょう:

コードをコピーします コードは次のとおりです:

function foo(x, y) {
var z = 30;
function bar() {} // 関数宣言
(function baz() {}) // 関数式
;
foo(10, 20);

「foo」関数コンテキストの次のアクティブ化オブジェクト (AO) は次のとおりです:

図 8. オブジェクトのアクティブ化

同様の理由で、AOには関数式は含まれていません。

この AO の詳細については、このチュートリアル シリーズの第 9 章を参照してください。

次にお話しするのは、3 番目の主要なオブジェクトです。ご存知のとおり、ECMAScript では内部関数 [内部関数] を使用し、その親関数変数またはグローバル変数を参照することがあります。これらの変数オブジェクトをコンテキストのスコープ オブジェクトと呼びます。前述のプロトタイプ チェーンと同様に、ここではスコープ チェーンと呼びます。
スコープ チェーン
スコープ チェーンは、コンテキストのコード内に出現する識別子を検索するオブジェクトのリストです。
スコープ チェーンは、オブジェクトのリスト (オブジェクトのリスト) です。 ) を使用して、コンテキスト コードに表示される識別子を取得します。
コードをコピー
スコープ チェーンの原理は、変数が独自のスコープに存在しない場合、最上位レベルまで親を探します。

識別子 [識別子] は、変数名、関数宣言、および通常のパラメーターとして理解できます。たとえば、関数が独自の関数本体内で変数を参照する必要があるが、その変数が関数内で宣言されていない (またはパラメーター名ではない) 場合、その変数は自由変数 [自由変数] と呼ばれます。次に、スコープ チェーンを使用してこれらの自由変数を検索する必要があります。

一般に、スコープ チェーンには、親変数オブジェクト (スコープ チェーンの先頭)、関数自身の変数 VO、およびアクティベーション オブジェクトが含まれます。ただし、場合によっては、with ステートメントや catch ステートメントなど、実行中にスコープ チェーンに動的に追加されるなど、他のオブジェクトも含まれることがあります。 [注釈: with-objects は、with ステートメントによって生成される一時スコープ オブジェクトを指します。catch-clauses は、例外オブジェクトを生成し、スコープの変更を引き起こす catch(e) などの catch 句を指します。

識別子を検索するときは、スコープ チェーンのアクティブ オブジェクト部分から開始し、次に (アクティブ オブジェクトで識別子が見つからない場合) スコープ チェーンの先頭から検索します。アクションと同じように、ドメインチェーンのように。

コードをコピーします コードは次のとおりです。

var x = 10; 🎜>( function foo() {
var y = 20;
(function bar() {
var z = 30;
// "x" と "y" は自由変数
// スコープチェーン内の次のオブジェクト (関数 "bar" の対話型オブジェクトの後)
console.log(x y z)
})()
}); ;


スコープ チェーンのオブジェクト リンケージは、スコープ チェーン内の次のオブジェクトを指す __parent__ と呼ばれる属性を通じて行われると想定します。このプロセスは Rhino コードでテストできます。このテクノロジーは実際に ES5 環境に実装されています (外部リンクと呼ばれるものがあります)。もちろん、単純なデータを使用してこのモデルをシミュレートすることもできます。 __parent__ の概念を使用すると、上記のコードを次の状況で実証できます。 (したがって、親変数は関数の [[Scope]] 属性に格納されます)。

図 9. スコープ チェーン

コードの実行中に with または catch ステートメントを使用すると、スコープ チェーンが変更されます。これらのオブジェクトは単純なオブジェクトであり、プロトタイプ チェーンも持ちます。この場合、スコープ チェーンは 2 次元で検索されます。

  1. まず、元のスコープチェーン内で
  2. 各リンク ポイントのスコープ チェーン (このリンク ポイントにプロトタイプがある場合)

次の例を見てみましょう:

コードをコピーします コードは次のとおりです:

Object.prototype.x = 10;

var w = 20;

// SpiderMonkey グローバル オブジェクト内:グローバルコンテキストの変数オブジェクト 「Object.prototype」から継承されます
// したがって、「未宣言のグローバル変数」を取得できます
// プロトタイプチェーンから取得できるため

console.log(x) ; // 10

(function foo() {

// "foo" はローカル変数です
var w = 40;
var x = 100;

// "x" は "Object.prototype" から取得できます。値は 10 であることに注意してください
// {z: 50} はそれから継承されるため

with ({z: 50 }) {
console.log(w, x, y, z) // 40, 10, 30, 50
}

// 「with」 " オブジェクトはスコープ チェーンから削除されます。その後
// x は再び foo のコンテキストから取得できます。今回は値が 100 に戻っていることに注意してください。
// "w" もローカル変数です
console.log(x, w); // 100, 40

// ブラウザ内
// 次のステートメントを通じてグローバル w 値を取得できます
console.log (window.w); // 20

})();
次のような構造図が得られます。これは、__parent__ を検索する前に、まず __proto__ へのリンクに移動することを意味します。

図 10. 増加したスコープ チェーン内

すべてのグローバル オブジェクトが Object.prototype から継承されるわけではないことに注意してください。上に示した状況は SpiderMonkey でテストできます。

すべての外部関数の変数オブジェクトが存在する限り、内部関数から外部データを参照することに関して特別なことは何もありません。スコープ リストを走査して必要な変数を見つけるだけです。ただし、上で述べたように、コンテキストが終了すると、その状態とそれ自体が破棄され、内部関数が外部関数から戻ります。さらに、返された関数は後で他のコンテキストでアクティブ化される可能性があるため、いくつかの自由変数を持つ以前に終了したコンテキストが再びアクティブ化された場合はどうなるでしょうか? 一般的に、この問題を解決する概念は ECMAScript の概念と直接関係しています。および (字句) クロージャと呼ばれます。
クロージャ
ECMAScript では、関数は「ファーストクラス」オブジェクトです。この用語は、関数を他の関数に引数として渡すことができることを意味します (この場合、関数は「funargs」と呼ばれます。これは「関数引数」の略です [注釈: 関数引数と適切に翻訳されているかどうかはわかりません]) 。 「funargs」を受け入れる関数は、高階関数、またはより数学的には演算子と呼ばれます。他の関数のランタイムも関数を返します。これらの返される関数は、関数値関数 (関数値を持つ関数) と呼ばれます。

「funargs」と「関数値」の間には 2 つの概念的な問題があり、これら 2 つの部分問題は「Funarg 問題」(「関数パラメータ問題」) と呼ばれます。関数パラメータの問題を正確に解決するには、クロージャの概念を導入する必要があります。これら 2 つの問題について詳しく説明します (ご覧のとおり、この問題を解決するために ECMAScript では関数の [[Scope]] 属性が使用されています)。

「funarg 問題」の副次問題は、「上向き funarg 問題」[注釈: 上向き探索関数パラメータ問題と訳される場合もあります] です。この問題は、ある関数が他の関数から外部に戻るときに発生します。外部コンテキストの終了時に外部コンテキストの変数にアクセスできるようにするには、内部関数が作成時(作成時)に [[Scope]] 属性の親要素のスコープに変数を格納する必要があります。次に、関数がアクティブ化されると、コンテキストのスコープ チェーンが [[Scope]] 属性と結合されたアクティブ化オブジェクトとして表示されます (実際、上の図で確認できます):

スコープ チェーン = アクティブ化オブジェクト[[ Scope]]
スコープ チェーン = アクティブ オブジェクト [[Scope]]

重要なことは、この保存されたロールにより、関数は作成時に外側のスコープを保存することに注意してください。スコープ チェーンは、将来の関数呼び出しでの変数検索に使用されます。

コードをコピー コードは次のとおりです。

function foo() {
var x = 10 ;
return function bar() {
console.log(x);
}

// "foo" も関数
を返します。 // この返された関数は内部変数 x

var returnsFunction = foo();

// グローバル変数 "x"
var x = 20;
// 返された関数をサポートします
returnedFunction(); // 結果は 20 ではなく 10 になります


この形式のスコープは静的スコープ [静的/字句スコープ] と呼ばれます。上記の x 変数は、関数バーの [[スコープ]] にあります。理論的には、動的スコープ [動的スコープ] も存在します。つまり、上記の x は 10 ではなく 20 として解釈されます。ただし、EMCAScript は動的スコープを使用しません。

もう 1 つのタイプの「funarg 問題」はトップダウン [「下向き funarg 問題」] です。この場合、親の上位と下位は存在しますが、変数の値を判断するときに存在します。 。つまり、この変数はどのスコープを使用すべきでしょうか?関数の作成時のスコープですか、それとも実行時のスコープですか?このあいまいさを回避するには、クロージャ、つまり静的スコープを使用します。

以下の例を参照してください:




コードをコピーします コードは次のとおりです。 :
// グローバル変数 "x"
var x = 10;

// グローバル関数
function foo() {
console.log( x);
}

(function (funArg) {

// ローカル変数 "x"
var x = 20;

// これは曖昧ではありません
// "foo" 関数の [[Scope]] に保存されたグローバル変数 "x" を使用するため、
// は呼び出し元スコープの "x" ではありません

funArg(); // 20 ではなく 10

})(foo); // foo を「funarg」として渡します


上記の状況から、静的スコープの使用は言語内のクロージャの必須要件であると結論付けることができるようです。ただし、一部の言語では、動的スコープと静的スコープの組み合わせが提供され、開発者が使用するスコープを選択できるようになります。ただし、ECMAScript では静的スコープのみが使用されます。したがって、ECMAScript は [[Scope]] 属性の使用を完全にサポートしています。クロージャの次の定義を取得できます。

クロージャは、コード ブロック (ECMAScript ではこれは関数) と、静的/字句的に保存されたすべての親スコープの組み合わせです。
したがって、これらの保存されたスコープを介して。関数は自由変数を簡単に参照できます。
クロージャーはコードのブロック (ECMAScript の関数) のシーケンスであり、すべての親のスコープを静的に保持します。関数内の自由変数は、これらの保存されたスコープを通じて検索されます。
コードをコピー
通常の関数は作成時に [[Scope]] を保存するため、理論的には ECMAScript 内のすべての関数がクロージャであることに注意してください。

もう 1 つの非常に重要な点は、複数の関数が同じ親スコープを持つ可能性があるということです (これは非常に一般的な状況です。たとえば、複数の内部関数またはグローバル関数が存在します)。この場合、[[スコープ]]内に存在する変数が共有されます。 1 つのクロージャで変数を変更すると、もう 1 つのクロージャにも影響します。
コードをコピー コードは次のとおりです。

function baz() {
var x = 1 ;
foo: 関数 foo() { return x; },
bar: 関数 bar() { return --x; }
}; 🎜>
var Closures = baz();

console.log(
closures.foo(), // 2
closures.bar() // 1
);


上記のコードは、次の図で表すことができます:


図 11. 共有 [[スコープ]]

上の図は、ループ内に複数の関数を作成するときに混乱を引き起こす可能性があります。作成した関数でループ変数 (「k」など) を使用すると、すべての関数で同じループ変数が使用されるため、一部のプログラマが期待した値を取得できないことがよくあります。なぜこのような問題が発生するのかは明らかです。すべての関数が同じ [[Scope]] を共有しており、ループ変数が最後の複雑な代入であるためです。


コードをコピーします コードは次のとおりです。 var data = [];
for (var k = 0; k data[k] = function () {
alert(k)>}

data[0](); // 3 ですが 0 ではありません
data[1]() // 3 ではありますが 1 ではありません
data[2](); // 3 ではありません2


この種の問題を解決するためのテクニックがいくつかあります。 1 つの手法は、関数を追加するなど、スコープ チェーンに追加のオブジェクトを提供することです。




コードをコピー

コードvar data = []; for (var k = 0; k data[k] = (関数( x) {
return function () {
alert(x)>}
})(k); // k をパラメータとして渡します
// 結果は正しいです
data[0]() // 0
data[1]() // 1
data[2](); 🎜>

クロージャ理論の詳細な研究と具体的な実践については、このチュートリアル シリーズの第 16 章「クロージャ」に記載されています。スコープ チェーンの詳細については、このチュートリアル シリーズの第 14 章「スコープ チェーン (スコープ チェーン)」を参照してください。

次の章では、実行コンテキストの最後の属性、つまりこのポインターの概念について説明します。

このポインタ
この値は、実行コンテキストに関連する特別なオブジェクトです。
したがって、コンテキスト オブジェクト (つまり、実行コンテキストが存在するコンテキスト内のオブジェクト) として名前を付けることができます。
これは、実行コンテキストと密接に関連する特別なオブジェクトです。したがって、コンテキストオブジェクト(実行コンテキストを起動するコンテキスト)と呼ぶこともできます。
コードをコピー
任意のオブジェクトをコンテキストの this 値として使用できます。 ECMAScript の実行コンテキスト、特にこれに関するいくつかの誤解をもう一度明らかにしたいと思います。多くの場合、これは変数オブジェクトのプロパティとして誤って説明されます。私は最近、たとえばこの本でそれを発見しました(本の中でこれについて言及している章は悪くありませんが)。

この値は実行コンテキストのプロパティですが、変数オブジェクトのプロパティではありません。
これは実行コンテキストのプロパティですが、変数オブジェクトのプロパティではありません。 🎜>コードをコピー
変数とは異なり、変数の検索に似たプロセスがないため、この機能は非常に重要です。コードで this を使用すると、this の値はスコープ チェーン内で検索せずに、実行コンテキストから直接取得されます。この値は、コンテキストに入るときの状況にのみ依存します。

ところで、ECMAScript とは異なり、Python には self パラメータがあり、これと似ていますが、実行中に変更できます。 ECMAScript では、これは変数ではないため、これに値を割り当てることはできません。

グローバル コンテキスト (グローバル コンテキスト) では、this の値はグローバル オブジェクトを参照します。つまり、this の値は変数そのものです。

コードをコピーします コードは次のとおりです。
var x = 10; 🎜>console .log(
x, // 10
this.x, // 10
window.x // 10
); 関数コンテキスト内[関数コンテキスト]、これは各関数呼び出しに応じて異なる値になる可能性があります。これは各呼び出し元によって提供され、呼び出し元は式 [呼び出し式] (つまり、この関数がどのようにアクティブ化され呼び出されるか) を呼び出すことによって生成されます。 。たとえば、次の例では foo が呼び出し先であり、グローバル コンテキストでアクティブ化されます。次の例は、呼び出し元の違いによって生じるこの違いを示しています。



コードをコピー

alert(this)>}

// caller activates "foo "この呼び出し先、
// そして、この呼び出し先に "this" を提供します

foo(); // グローバル オブジェクト
foo.prototype.constructor(); // foo.prototype

var bar = {
baz: foo
};

bar.baz(); // バー

(bar.baz); // これも bar
(bar.baz = bar.baz)(); // これはグローバル オブジェクトです
(bar.baz, bar.baz)(); // これもグローバル オブジェクトです
(false || bar.baz)(); // グローバル オブジェクトでもあります

var otherFoo = bar.baz;
otherFoo(); // 依然としてグローバル オブジェクトです>
さらに深く考えたい場合は、各関数呼び出しで this の値がどのように変化するか (さらに重要なのは、どのように変化するか) について、この一連のチュートリアルの第 10 章を読むことができます。上記の状況については、この章で詳しく説明します。

結論
これで簡単な概要が完了しました。それほど簡単ではないように思えるかもしれませんが、これらのトピックを完全に扱うには 1 冊の本が必要になります。触れなかった重要なトピックが 2 つあります。それは、関数 (および、関数宣言や関数式など、さまざまな種類の関数の違い) と ECMAScript の評価戦略です。これら 2 つのトピックは、それぞれこのチュートリアル シリーズの第 15 章「関数」と第 19 章「評価戦略 (評価戦略)」にあります。

コメント、質問、追加がある場合は、記事のコメントで議論していただければ幸いです。

皆さんの ECMAScript 学習の成功を祈っています。
関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート