ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScriptを深く理解するシリーズ(13) そう、これ!_javascriptスキル

JavaScriptを深く理解するシリーズ(13) そう、これ!_javascriptスキル

WBOY
WBOYオリジナル
2016-05-16 17:56:53843ブラウズ

はじめに
この記事では、実行コンテキストに直接関連する詳細について説明します。話題になっているのはこのキーワードです。このトピックは難しいことが実際に証明されており、さまざまな実行コンテキストでこれを決定する際に問題が発生することがよくあります。

多くのプログラマは、プログラミング言語において this キーワードはオブジェクト指向プログラム開発と密接に関連しており、コンストラクターによって新しく作成されたオブジェクトを完全に指すものであると信じ込んでいます。これは ECMAScript 仕様にも実装されていますが、後で説明するように、ECMAScript では、これは新しく作成されたオブジェクトを指すことだけに限定されません。

英語翻訳: Dmitry A. Soshnikov、Stoyan Stefanov の協力
発行日: 2010-03-07
http://dmitrysoshnikov.com/ecmascript/chapter-3-this/

ロシア語原文: Dmitry A. Soshnikov
訂正: Zerogif
公開: 2009-06-28;
更新: 2010-03-07
http://dmitrysoshnikov.com/ecmascript /ru-chapter-3-this/

この記事の内容の大部分は、http://www.denisdeng.com/?p=900 を参照しています。
一部の文は、justin の中国語翻訳を参照しています。
コードをコピー
これが ECMAScript で正確に何であるかを詳しく見てみましょう。

定義
これは実行コンテキストの属性です:

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

activeExecutionContext = {
VO: {...},
this: thisValue
};

ここでの VO は前の章の変数オブジェクトで説明しました。

これはコンテキスト内の実行可能コードのタイプに直接関係しており、この値はコンテキストに入るときに決定され、コンテキストの実行中は変更されません。

これらのケースをさらに詳しく調べてみましょう:

これはグローバル コードです
ここではすべてが簡単です。グローバル コードでは、これは常にグローバル オブジェクト自体であるため、間接的に参照することができます。
コードをコピー コードは次のとおりです:

// グローバルを定義する属性を表示しますobject
this .a = 10; // global.a = 10
alert(a); // 10

// 識別されない
b = 20; 🎜> alter(this.b); // 20

// グローバル コンテキストの変数オブジェクトはグローバル オブジェクト自体であるため、
// も暗黙的に宣言されます。 = 30;
alert(this.c); // 30


関数コードで this を使用する場合は興味深いですが、この状況は困難であり、たくさんの問題。

このタイプのコードにおけるこの値の最初の (おそらく最も重要な) 特徴は、値が関数に静的にバインドされていないことです。

上で述べたように、これは関数コードに入るときに決定され、この値は毎回まったく異なります。

いずれの場合も、コードの実行中は this の値は変更されません。つまり、変数ではないため、新しい値を割り当てることはできません (対照的に、Python ではプログラミング言語。オブジェクト自体として明確に定義されており、実行時に継続的に変更できます。)



コードをコピー コードは次のとおりです。 var foo = {x: 10};
var bar = {
x: 20,
test: function () {

alert(this === bar) // true
alert(this); .x) ; // 20

this = foo; // エラー、this の値はいつでも変更できません

alert(this.x); //エラー。20 ではなく 10 である必要があります。

}

};

// コンテキスト
// これはバー オブジェクトとみなされます
// "bar" オブジェクトとして決定される; その理由 -
// 以下で詳しく説明します

bar.test(); // true、20

foo。 test = bar.test;

// ただし、これは foo ではありません
// 同じ関数が呼び出されますが、

foo.test(); // false 10


したがって、関数コード内のこの値の変更に影響を与える要因がいくつかあります。

まず、通常の関数呼び出しでは、これはコンテキスト コードをアクティブにする呼び出し元によって提供されます。つまり、関数の親コンテキストを呼び出します。これは関数の呼び出し方法によって異なります。

どのような状況でも this の値を正確に判断するには、この重要な点を理解し、覚えておく必要があります。呼び出しのコンテキストで this 値に影響を与えるのは関数の呼び出し方法であり、他には何も影響しません (一部の記事や JavaScript に関する本でさえ、「this 値は関数の定義方法に依存する」と主張されています) 、グローバル関数の場合、これはグローバル オブジェクトに設定されますが、関数がオブジェクトのメソッドの場合、これは常にオブジェクトを指します - これは絶対に当てはまりません)。トピックを続けると、通常のグローバル関数でも、さまざまな形式の呼び出しメソッドによってアクティブ化され、異なる this 値が得られることがわかります。
コードをコピー コードは次のとおりです。

function foo() {
alert (これ) ;
}

foo(); // グローバル

alert(foo === foo.prototype.constructor) // true

/ / しかし、同じ 関数の呼び出し式が異なる場合、これは異なります

foo.prototype.constructor(); // foo.prototype

は定義されたメソッドとして使用できます一部のオブジェクトによって関数が呼び出されますが、これはこのオブジェクトに設定されません。
コードをコピー コードは次のとおりです:

var foo = {
bar: function () {
alert(this);
alert(this === foo)

foo.bar();

var exampleFunc = foo.bar;

alert(exampleFunc === foo.bar); // true

// 同じものに対する別の呼び出し式function 、これは異なります

exampleFunc(); // global, false


では、関数の呼び出し方法は this の値にどのように影響するのでしょうか?この値の決定を完全に理解するには、その内部型の 1 つである参照型 (Reference 型) を詳細に分析する必要があります。

参照型 (参照型)
疑似コードを使用すると、参照型の値を 2 つのプロパティを持つオブジェクトとして表すことができます。base (つまり、プロパティを持つオブジェクト) と propertyName の Base です。 。


var valueOfReferenceType = {
base: ,
propertyName:


参照型の値には 2 つの状況しかありません:
識別子
またはプロパティ アクセサー
識別子の処理については、次の記事で詳しく説明します。ここで知っておく必要があるのは、このアルゴリズムの戻り値が常に参照型の値であることだけです (これは、これは言うことが重要です)。

識別子は、グローバル オブジェクト内の変数名、関数名、関数パラメータ名、および認識されないプロパティ名です。たとえば、次の識別子の値:



function bar() {}


演算の中間結果の参照型に対応する値は次のとおりです:


コードをコピー コードは次のとおりです: var fooReference = {
base: global,
propertyName : 'foo'
};

var barReference = {
base: global,
propertyName: 'bar'
};疑似コードで参照型からオブジェクトの実際の値を取得するには GetValue メソッドは次のように記述できます:



コードをコピー

コードは次のとおりです。 function GetValue(value) { if (Type(value) != Reference) { return value;
varbase = GetBase(value);

if (base === null) {
throw new ReferenceError;

returnbase.[ [Get]](GetPropertyName(value));

}


内部 [[Get]] メソッドは、継承されたプロパティの分析を含む、オブジェクトのプロパティの実際の値を返します。プロトタイプチェーン。




コードをコピー

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

GetValue(fooReference); // 10
GetValue(barReference); // 関数オブジェクト "bar"

属性アクセサーはすべておなじみのはずです。これには、ドット (.) 構文 (プロパティ名が正しい識別子で、事前にわかっている場合) と括弧構文 ([]) の 2 つのバリエーションがあります。
コードをコピーします コードは次のとおりです。

foo.bar(); foo[' bar']();

中間計算の戻り値には参照型の値が入っています。

コードをコピー コードは次のとおりです。
var fooBarReference = {
base: foo,
propertyName: 'bar'
};

GetValue(fooBarReference); // 関数オブジェクト "bar"

参照型の値関数コンテキスト内のこの値はどのように関連しているのでしょうか? —最も重要な意味で。 この関連付けのプロセスがこの記事の中心です。 関数コンテキストで this の値を決定するための一般的な規則は次のとおりです。

関数コンテキストでは、this は呼び出し元によって提供され、関数の呼び出し方法によって決定されます。呼び出し括弧 () の左側が参照型の値である場合、これは参照型値の基本オブジェクトに設定されます。それ以外の場合 (参照型とは異なるその他のプロパティ)、この値は null になります。 。ただし、this の値が null である実際の状況はありません。this の値が null の場合、その値は暗黙的にグローバル オブジェクトに変換されるからです。注: ECMAScript の第 5 版では、グローバル変数への変換は強制されなくなり、未定義に割り当てられます。

この例のパフォーマンスを見てみましょう:

コードをコピーします コードは次のとおりです。
function foo() {
return this;
}

foo(); // グローバル

呼び出し括弧の左側は Reference 型の値です (foo は識別子であるため)。

コードをコピー コードは次のとおりです。
var fooReference = {
base: global,
propertyName: 'foo'
};

同様に、これも参照型の基本オブジェクトに設定されます。それがグローバルオブジェクトです。

同様に、プロパティ アクセサーを使用します:

コードをコピーします コードは次のとおりです:
var foo = {
bar: function () {
return this;
}
} // foo

ここでも foo オブジェクトをベースとする参照型があり、これは関数 bar がアクティブ化されたときに使用されます。



コードをコピー
コードは次のとおりです。 var fooBarReference = { base: foo,
propertyName: 'bar'
};


ただし、同じ関数を別の形式でアクティブにすると、他の this 値が取得されます。



コードをコピーします
コードは次のとおりです。 var test = foo.bar; >test( ); // global
test は識別子として使用されるため、参照型の他の値が生成され、そのベース (グローバル オブジェクト) がこの値として使用されます。



コードをコピー
コードは次のとおりです。 var testReference = { base: global, propertyName: 'test'
};


これで、異なる形式の式で同じ関数をアクティブ化すると、この値が異なる理由が明確にわかります。さまざまな参照型 (参照型) の中間値。



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

function foo() {
alert(this);

foo(); // グローバルなので、

var fooReference = {
base: global,
propertyName: 'foo'
};

alert(foo === foo.prototype.constructor) // true

//式を呼び出す形式

foo.prototype.constructor(); // foo.prototype、なぜなら

var fooPrototypeConstructorReference = {
base: foo.prototype,
propertyName : 'constructor'
};

次の呼び出しによって this の値を動的に決定する別の典型的な例:

コードをコピー コードは次のとおりです。
function foo() {
alert(this.bar)
}

var x = {バー: 10};
var y = {bar: 20};

x.test = foo; // 10
y.test(); // 20


関数呼び出しと非参照型
したがって、前述したように、呼び出し括弧の左側がが参照型ではなく他の型である場合、この値は自動的に null に設定され、結果はグローバル オブジェクトになります。
この式についてもう一度考えてみましょう:




コードをコピーします
コードは次のとおりです: (function () { alert(this); // null => global
})();


この例では、関数オブジェクト ただし、参照型のオブジェクトではありません (識別子やプロパティ アクセサーではありません)。したがって、 this の値は最終的にグローバル オブジェクトに設定されます。

より複雑な例:




コードをコピーします
コードは次のとおりです: var foo = { bar: function () {
alert(this)>}
}; // 参照、OK = > foo
(foo.bar)() // 参照、OK => foo

(foo.bar = foo.bar)(); || foo.bar)(); // グローバル?


にプロパティ アクセサがあるのはなぜですか?その途中 値は参照型の値である必要があります。一部の呼び出しでは、取得する this 値はベース オブジェクトではなく、グローバル オブジェクトですか?

問題は、次の 3 つの呼び出しでは、特定の操作を適用した後、呼び出し括弧の左側の値が参照型ではなくなることです。

最初の例は明白です - 明らかな参照型です。その結果、これが基本オブジェクト、つまり foo になります。
2 番目の例では、グループ演算子は適用されません。GetValue など、参照型からオブジェクトの実際の値を取得する上記のメソッドを考えてください。同様に、グループ操作の戻りでも参照型を取得します。これが、 this 値がベース オブジェクト (foo) に再度設定される理由です。
3 番目の例では、グループ演算子とは異なり、代入演算子は GetValue メソッドを呼び出します。返される結果は関数オブジェクト (参照型ではありません) です。つまり、これは null に設定され、結果はグローバル オブジェクトになります。
4 番目と 5 番目も同様です。コンマ演算子と論理演算子 (OR) が GetValue メソッドを呼び出し、それに応じて参照が失われ、関数が取得されます。そして、再度グローバルに設定します。
参照型であり、これは null
次のような状況があります。呼び出し側の式が呼び出し括弧の左側で参照型の値を制限している場合、これが null に設定されているにもかかわらず、結果は変換されますグローバルへ。この状況は、参照型値の基本オブジェクトがアクティブ オブジェクトである場合に発生します。

次の例では、内部関数が親関数によって呼び出されます。このとき、上記の特殊な状況がわかります。第 12 章で学んだように、ローカル変数、内部関数、および仮パラメータは、特定の関数のアクティベーション オブジェクトに格納されます。




コードをコピー


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

function foo() { function bar() { alert(this); // グローバル } bar() // AO.bar() }

アクティブなオブジェクトは常に this として返され、値は null です (つまり、疑似コードの AO.bar() は null.bar() と同等です)。ここで、これをグローバル オブジェクトに設定して、上で説明した例に戻ります。

例外が 1 つあります。with オブジェクトに関数名属性が含まれている場合、その関数は with ステートメントの内部ブロックで呼び出されます。 With ステートメントは、オブジェクトのスコープの前、つまりアクティブなオブジェクトの前に追加されます。同様に、(識別子またはプロパティ アクセサーを介した) 参照型があり、その基本オブジェクトはアクティブ オブジェクトではなく、with ステートメントのオブジェクトになります。ちなみに、with オブジェクトはスコープ チェーンの最前面のオブジェクト (グローバル オブジェクトまたはアクティブ オブジェクト) よりも前にあるため、これは内部関数だけでなくグローバル関数にも関連します。
コードをコピーします コードは次のとおりです。

var x = 10; 🎜>with ({

foo: function () {
alert(this.x);
},
x: 20

}) {

foo(); // 20

}

// なぜなら

var fooReference = {
base: __withObject,
propertyName: 'foo '
};


catch ステートメントの関数呼び出しの実際のパラメーターでも同じ状況が発生します。この場合、catch オブジェクトはスコープの前、つまり、アクティブなオブジェクトまたはグローバル オブジェクトのフロント。ただし、この特定の動作は ECMA-262-3 のバグであることが確認されており、ECMA-262-5 の新しいバージョンで修正されています。このようにして、特定のアクティブ オブジェクトで、this はグローバル オブジェクトを指します。 catch オブジェクトではなく。


try {
throw function () {
alert(this);
} catch (e) {
e(); // ES3 標準は __catchObject、ES5 標準はグローバル
}

// アイデアについて

var eReference = {
base: __catchObject,
propertyName: 'e'
}; これは新しいバージョンで修正されました。 ES5 標準 このバグ、
// つまり、これはグローバル オブジェクトです。
var eReference = {
base: global,
propertyName: 'e'
}; 🎜> 名前付き関数の再帰呼び出しでも同じ状況が発生します (関数の詳細については、第 15 章関数を参照してください)。関数の最初の呼び出しでは、ベース オブジェクトは親アクティビティ オブジェクト (またはグローバル オブジェクト) です。再帰呼び出しでは、ベース オブジェクトは関数式のオプションの名前を格納する特定のオブジェクトである必要があります。ただし、この場合、これは常にグローバル オブジェクトを指します。




コードをコピー

コードは次のとおりです。
(function foo(bar) { alert(this); !bar && foo(1) // 特別なオブジェクトである必要がありますが、常に (正しい) グローバル
}); / global


これは、コンストラクターとして呼び出される関数内です。
この値に関連する別の状況は、関数のコンテキスト、つまりコンストラクターの呼び出しです。




コードをコピー


コードは次のとおりです。
この例では、new 演算子は「A」関数の内部 [[Construct]] メソッドを呼び出し、オブジェクトの作成後に内部 [[ []メソッドを呼び出します。 まったく同じ関数「A」は、新しく作成されたオブジェクトに this の値を設定します。


関数呼び出しで this を手動で設定する

関数プロトタイプで定義された 2 つのメソッド (すべての関数がそれにアクセスできるように) を使用すると、関数呼び出しで this 値を手動で設定できます。 .apply メソッドと .call メソッドです。これらは this 値として受け入れる最初の引数を使用し、これは呼び出しスコープで使用されます。これら 2 つのメソッドの違いは非常にわずかです。.apply の場合、2 番目のパラメータは配列 (または引数などの配列のようなオブジェクト) である必要があります。同様に、.call は任意のパラメータを受け入れることができます。両方のメソッドに必要なパラメータは次のとおりです。最初の ——これ。

例:



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


var b = 10;
関数 a(c) { alert(this.b) } a(20) ; // this === グローバル, this.b == 10, c == 20 a.call({b: 20}, 30); {b: 20} , this.b == 20, c == 30
a.apply({b: 30}, [40]) // this === {b: 30}, this.b = = 30、c = = 40


結論
この記事では、ECMAScript におけるこのキーワードの特性について説明しました (C や Java と比較すると、これらは確かに特性です)。この記事が、ECMAScript で this キーワードがどのように機能するかを正確に理解するのに役立つことを願っています。繰り返しになりますが、コメント欄でご質問にお答えいただければ幸いです。

その他の参考資料
声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。