ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript_Basics でのスコープとコンテキストの使用法の概要

JavaScript_Basics でのスコープとコンテキストの使用法の概要

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

JavaScript のスコープとコンテキストは、JavaScript がもたらす柔軟性のおかげで、この言語に固有です。各関数には異なる変数コンテキストとスコープがあります。これらの概念は、JavaScript のいくつかの強力なデザイン パターンの基礎となっています。ただし、これは開発者に大きな混乱をもたらします。以下では、JavaScript におけるコンテキストとスコープの違いと、さまざまなデザイン パターンでそれらがどのように使用されるかを包括的に明らかにします。

コンテキストとスコープ

最初に明確にする必要があるのは、コンテキストとスコープは異なる概念であるということです。長年にわたり、多くの開発者がこれら 2 つの用語を混同し、一方をもう一方と誤って説明していることに気づきました。公平を期すために言うと、これらの用語は非常にわかりにくいものになっています。

各関数呼び出しには、スコープとそれに関連付けられたコンテキストがあります。基本的に、スコープは関数ベースであり、コンテキストはオブジェクトベースです。言い換えれば、スコープは各関数呼び出しでの変数へのアクセスに関連しており、各呼び出しは独立しています。コンテキストは常にキーワード this の値であり、現在の実行可能コードを呼び出すオブジェクトへの参照です。

変数スコープ

変数はローカル スコープまたはグローバル スコープで定義でき、その結果、異なるスコープからランタイム変数にアクセスできます。グローバル変数は関数本体の外側で宣言する必要があり、実行中のプロセス全体に存在し、どのスコープでもアクセスおよび変更できます。ローカル変数は関数本体内でのみ定義され、関数呼び出しごとに異なるスコープを持ちます。このトピックは呼び出し内のみの値の割り当て、評価、操作であり、スコープ外の値にはアクセスできません。

現在、JavaScript はブロックレベルのスコープをサポートしていません。ブロックレベルのスコープとは、if ステートメント、switch ステートメント、loop ステートメントなどのステートメント ブロック内の変数の定義を指します。これは、ステートメントの外部で変数にアクセスできないことを意味します。ブロック。現在、ステートメント ブロック内で定義されている変数は、ステートメント ブロックの外部からアクセスできます。ただし、let キーワードが ES6 仕様に正式に追加されたため、これは間もなく変更されます。ローカル変数をブロックレベルのスコープとして宣言するには、var キーワードの代わりにこれを使用します。

「この」コンテキスト

コンテキストは通常​​、関数の呼び出し方法によって異なります。関数がオブジェクトのメソッドとして呼び出される場合、これはメソッドが呼び出されるオブジェクトに設定されます:

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

var object = {
foo: function(){
alert(this === object); >};

object.foo(); // true


new 演算子を使用してオブジェクトのインスタンスを作成する関数を呼び出す場合にも同じ原則が適用されます。この方法で呼び出すと、this の値は新しく作成されたインスタンスに設定されます:


function foo(){
alert(this);

foo() // ウィンドウ
new foo() // foo


アンバインド関数を呼び出すと、これはデフォルトでグローバル コンテキストまたはウィンドウ オブジェクト (ブラウザー内の場合) に設定されます。ただし、関数が strict モード (「use strict」) で実行される場合、この値はデフォルトで undefine に設定されます。
実行コンテキストとスコープ チェーン

JavaScript はシングルスレッド言語です。つまり、ブラウザーで同時に実行できる処理は 1 つだけです。 JavaScript インタープリターが最初にコードを実行するとき、最初はデフォルトでグローバル コンテキストが使用されます。関数が呼び出されるたびに、新しい実行コンテキストが作成されます。

ここでの「実行コンテキスト」という用語は、上で説明したコンテキストではなくスコープを意味します。これは不適切な命名ですが、この用語は ECMAScript 仕様によって定義されており、それに従う以外に選択肢はありません。

新しい実行コンテキストが作成されるたびに、スコープ チェーンの先頭に追加され、実行スタックまたは呼び出しスタックにもなります。ブラウザは常に、スコープ チェーンの最上位にある現在の実行コンテキストで実行されます。完了すると、それ (現在の実行コンテキスト) はスタックの最上位から削除され、制御は前の実行コンテキストに戻ります。例:



コードをコピー コードは次のとおりです。 function first(){
2番目( );
関数2番目(){
3番目();
関数3番目(){
関数4番目();何か
}
}
}
}
first();


前のコードを実行すると、ネストされた関数が上から下に 4 番目の関数まで実行されます。このとき、スコープ チェーンは上から下に 4 番目、3 番目、2 番目、1 番目、グローバルになります。 4 番目の関数は、グローバル変数と、独自の変数と同様に、1 番目、2 番目、および 3 番目の関数で定義された変数にアクセスできます。 4 番目の関数の実行が完了すると、4 番目のコンテキストがスコープ チェーンの先頭から削除され、実行は 3 番目の関数に戻ります。このプロセスは、すべてのコードの実行が完了するまで継続されます。

異なる実行コンテキスト間の変数名の競合は、ローカルからグローバルまでスコープ チェーンを登ることによって解決されます。これは、同じ名前のローカル変数がスコープ チェーン内でより高い優先順位を持つことを意味します。

簡単に言えば、関数実行コンテキストで変数にアクセスしようとするたびに、検索プロセスは常に独自の変数オブジェクトから開始されます。探している変数が独自の変数オブジェクト内に見つからない場合は、スコープ チェーンの検索を続けます。スコープ チェーンをたどり、各実行コンテキスト変数オブジェクトを調べて、変数名に一致する値を見つけます。

クロージャ

入れ子の関数がその定義 (スコープ) の外でアクセスされ、外側の関数が戻った後に実行できるようにすると、クロージャが形成されます。これ (クロージャ) は、外部関数のローカル変数、引数、関数宣言への (内部関数内での) アクセスを維持します。カプセル化により、実行コンテキストを外部スコープから隠して保護しながら、さらなる操作を実行できるパブリック インターフェイスを公開できます。簡単な例は次のようになります。
コードをコピーします。 コードは次のようになります。

function foo() {
var local = 'プライベート変数';
return function bar(){
return local;
}

var getLocalVariable = foo() ;
getLocalVariable() // プライベート変数

最も一般的なクロージャーの種類の 1 つは、よく知られたモジュール パターンです。これにより、パブリック、プライベート、および特権メンバーを模擬することができます:

コードをコピーします コードは次のとおりです:
var Module = (function(){
var privateProperty = 'foo';

function privateMethod(args){
//何かをする
}

return {

publicProperty: "",

publicMethod: function(args){
//何かをする
},

privilegedMethod: function(args) ){
privateMethod(args);
}
}
})();

モジュールは実際には括弧のペアを追加することでシングルトンに似ています。説明の最後に プロセッサが解釈を終えたらすぐに実行する(関数をすぐに実行する)。クロージャ実行コンテキストで使用できる外部メンバーは、返されたオブジェクトのパブリック メソッドとプロパティ (Module.publicMethod など) だけです。ただし、実行コンテキストは保護され (クロージャ)、変数との対話はパブリック メソッドを通じて行われるため、すべてのプライベート プロパティとメソッドはプログラムのライフ サイクルを通じて存在します。

別のタイプのクロージャは、即時呼び出し関数式 IIFE と呼ばれます。これは、ウィンドウ コンテキストで自己呼び出しされる匿名関数にすぎません。

コードをコピー コードは次のとおりです:
function(window){

var a = 'foo', b = 'bar';

function private(){
// 何かを実行します
}

window.Module = {

public: function(){
// 何かをします
}
}

})(this);グローバル名前空間。この式は非常に便利です。関数本体内で宣言されたすべての変数はローカル変数であり、クロージャを通じてランタイム環境全体にわたって持続します。ソース コードをカプセル化するこの方法は、プログラムとフレームワークの両方で非常に一般的であり、通常は外部と対話するための単一のグローバル インターフェイスを公開します。

Call と apply


は、カスタム コンテキストで関数を実行できるようにするすべての関数に組み込まれている 2 つの単純なメソッドです。 call 関数にはパラメータ リストが必要で、apply 関数ではパラメータを配列として渡すことができます。


コードをコピー
コードは次のとおりです: function user(first, last, age){ // 何かをします
}
user.call(window, 'John', 'Doe' , 30);
user.apply (ウィンドウ, ['ジョン', 'Doe', 30]);
実行結果は同じです。ユーザー関数はウィンドウ コンテキストで呼び出され、同じ 3 つのパラメーターが提供されます。

ECMAScript 5 (ES5) では、コンテキストを制御する Function.prototype.bind メソッドが導入され、この関数 (コンテキスト) は、バインド メソッドの最初のパラメーターに永続的にバインドされます。関数の呼び出し方法。クロージャを通じて関数のコンテキストを修正します。サポートされていないブラウザの場合の解決策は次のとおりです。
コードをコピーします。 コードは次のとおりです。

if(!('bind' in Function.prototype)){
Function.prototype.bind = function(){
var fn = this, context = argument[ 0] 、args = Array.prototype.slice.call(arguments, 1);
return function(){
return fn.apply(context, args);
}


これは、オブジェクト指向やイベント処理などのコンテキスト損失でよく使用されます。これが必要なのは、ノードの addEventListener メソッドがイベント ハンドラーがバインドされているノードとして関数実行のコンテキストを常に維持するためであり、これが重要です。ただし、高度なオブジェクト指向技術を使用し、コールバック関数のコンテキストをメソッドのインスタンスとして維持する必要がある場合は、コンテキストを手動で調整する必要があります。これはバインドによってもたらされる利便性です:


関数MyClass() {
this.element = document.createElement('div');
this.element.addEventListener('click', this.onClick.bind(this), false);
🎜>
MyClass.prototype.onClick = function(e){
// do something
};


バインド関数のソース コードを振り返ると、次の行は、配列のメソッドを呼び出す比較的単純なコードであることに気づくかもしれません:


コードをコピー コードは次のとおりです: Array.prototype.slice.call(arguments, 1);

興味深いことに、ここで、引数オブジェクトは実際には配列ではないことに注意してください。多くの場合、配列のような ) オブジェクトとして説明されます。これは、nodelist (document.getElementsByTagName() メソッド) によって返される結果です。これらには長さ属性が含まれており、値にインデックスを付けることができますが、スライスやプッシュなどのネイティブの配列メソッドをサポートしていないため、配列ではありません。ただし、配列と同様に動作するため、配列メソッドを呼び出してハイジャックすることができます。配列のようなコンテキストで配列メソッドを実行する場合は、上記の例に従ってください。
他のオブジェクト メソッドを呼び出すこの手法は、JavaScript で古典的な継承 (クラス継承) をエミュレートするときにオブジェクト指向にも適用されます:



コードをコピー コードは次のとおりです。 MyClass.prototype.init = function(){
// 「MyClass」のコンテキストでスーパークラスの init メソッドを呼び出します。 " インスタンス
MySuperClass.prototype.init.apply(this, argument);
}


サブクラス (MyClass) のインスタンスでスーパークラス (MySuperClass) のメソッドを呼び出すことによって、この強力なデザインパターンを再現できます。

結論

最新の JavaScript ロールではスコープとコンテキストが重要かつ基本的な役割を果たすため、高度なデザイン パターンを学習し始める前にこれらの概念を理解することが非常に重要です。クロージャ、オブジェクト指向、継承、またはさまざまなネイティブ実装について話す場合でも、コンテキストとスコープが重要な役割を果たします。 JavaScript 言語をマスターし、そのコンポーネントを深く理解することが目標の場合、スコープとコンテキストを出発点にする必要があります。

翻訳者の補足

作者が実装したバインド関数は、bind によって返される関数を呼び出すときにパラメータを渡すことができません。次のコードはこの問題を修正します。 🎜>



コードをコピーします
コードは次のとおりです: if(!('bind' Function.prototype)){ Function.prototype.bind = function(){
var fn = this, context = argument[0], args = Array.prototype.slice.call(arguments, 1);
return function(){
return fn.apply(context, args.concat(arguments));//修正
}
}
}

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