一言で言えばJavascript クロージャは、関数がlexical-parent function で宣言された変数にアクセスできるようにします。
詳しい説明を見てみましょう。クロージャを理解するには、JavaScript がどのように変数をスコープするかを理解することが重要です。
スコープ
JavaScript では、スコープは関数で定義されます。すべての関数は新しいスコープを定義します。
次の例を考えてみましょう。
function f()
{//begin of scope f
var foo='hello'; //foo is declared in scope f
for(var i=0;i<2;i++){//i is declared in scope f
//the for loop is not a function, therefore we are still in scope f
var bar = 'Am I accessible?';//bar is declared in scope f
console.log(foo);
}
console.log(i);
console.log(bar);
}//end of scope f
fプリントを呼び出す
hello
hello
2
Am I Accessible?
g
別の関数内で定義された関数がある場合を考えてみましょうf
。
function f()
{//begin of scope f
function g()
{//being of scope g
/*...*/
}//end of scope g
/*...*/
}//end of scope f
f
のレキシカルな親を呼び出しますg
。前に説明したように、2 つのスコープがあります。スコープf
とスコープg
。
しかし、一方のスコープはもう一方のスコープの「内」にあるので、子関数のスコープは親関数のスコープの一部ですか? 親関数のスコープで宣言された変数はどうなりますか。子関数のスコープからそれらにアクセスできますか? それがまさにクロージャーが介入する場所です。
閉鎖
JavaScript では、関数はスコープで宣言されたg
変数にアクセスできるだけでなくg
、親関数のスコープで宣言された変数にもアクセスできますf
。
以下を検討してください。
function f()//lexical parent function
{//begin of scope f
var foo='hello'; //foo declared in scope f
function g()
{//being of scope g
var bar='bla'; //bar declared in scope g
console.log(foo);
}//end of scope g
g();
console.log(bar);
}//end of scope f
fプリントを呼び出す
hello
undefined
行を見てみましょうconsole.log(foo);
。この時点でスコープ内にあり、スコープ内で宣言されてg
いる変数にアクセスしようとします。しかし、前に述べたように、レキシカルな親関数で宣言された任意の変数にアクセスできます。のレキシカルな親です。したがって、印刷されます。
行を見てみましょう。この時点でスコープ内にあり、スコープ内で宣言されている変数にアクセスしようとします。は現在のスコープで宣言されておらず、関数は の親ではないため、未定義ですfoo
f
g
f
hello
console.log(bar);
f
bar
g
bar
g
f
bar
実際、レキシカルな「親の親」関数のスコープで宣言された変数にアクセスすることもできます。したがって、関数h
内で定義された関数がある場合g
function f()
{//begin of scope f
function g()
{//being of scope g
function h()
{//being of scope h
/*...*/
}//end of scope h
/*...*/
}//end of scope g
/*...*/
}//end of scope f
関数、、およびh
のスコープで宣言されたすべての変数にアクセスできます。これはクロージャで行われます。JavaScriptクロージャーでは、レキシカル親関数、レキシカル親親関数、レキシカル親親関数などで宣言された任意の変数にアクセスできます。これはスコープ チェーンと見なすことができます。字句親を持たない最後の親関数まで。h
g
f
scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ...
ウィンドウ オブジェクト
実際、チェーンは最後の親関数で停止しません。もう 1 つ特別なスコープがあります。グローバルスコープ。関数で宣言されていないすべての変数は、グローバル スコープで宣言されていると見なされます。グローバル スコープには 2 つの特殊性があります。
- グローバルスコープで宣言されたすべての変数は、どこからでもアクセスできます
- グローバル スコープで宣言された変数は、
window
オブジェクトのプロパティに対応します。
foo
したがって、グローバル スコープで変数を宣言するには、正確に 2 つの方法があります。関数で宣言しないかfoo
、window オブジェクトのプロパティを設定します。
どちらの試みもクロージャーを使用しています
より詳細な説明を読んだので、両方のソリューションがクロージャを使用していることは明らかです。しかし念のため証明しておきましょう。
新しいプログラミング言語を作成しましょう。JavaScript-非閉鎖。名前が示すように、JavaScript-No-Closure は JavaScript と同じですが、Closure をサポートしていません。
言い換えると;
var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello
では、JavaScript-No-Closure を使用した最初のソリューションで何が起こるか見てみましょう。
for(var i = 0; i < 10; i++) {
(function(){
var i2 = i;
setTimeout(function(){
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}, 1000)
})();
}
したがって、これはundefined
JavaScript-No-Closure で 10 回出力されます。
したがって、最初の解決策は閉鎖を使用します。
2 番目の解決策を見てみましょう。
for(var i = 0; i < 10; i++) {
setTimeout((function(i2){
return function() {
console.log(i2); //i2 is undefined in JavaScript-No-Closure
}
})(i), 1000);
}
したがって、これはundefined
JavaScript-No-Closure で 10 回出力されます。
どちらのソリューションもクロージャを使用しています。
編集: これら 3 つのコード スニペットは、グローバル スコープで定義されていないと想定されます。それ以外の場合、変数foo
とi
はオブジェクトにバインドされるため、JavaScript と JavaScript-No-Closure の両方でオブジェクトをwindow
介してアクセスできます。window