ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript クロージャーについての深い理解

JavaScript クロージャーについての深い理解

小云云
小云云オリジナル
2017-12-06 15:16:061584ブラウズ

クロージャには、自由な (特定のオブジェクトにバインドされていない) 変数が含まれています。これらの変数は、このコード ブロックやグローバル コンテキスト内では定義されませんが、コード ブロックが定義されている環境 (ローカル変数) で定義されます。 「クロージャ」という言葉は、実行されるコードのブロック (自由変数がブロック内に含まれるため、これらの自由変数とそれらが参照するオブジェクトは解放されません) と自由変数に提供されるバインディングの組み合わせに由来します。コンピューティング環境 (範囲)。この記事では、JavaScript クロージャについて詳しく説明します。

1. 変数のスコープ

クロージャを理解するには、まず Javascript の特殊な変数スコープを理解する必要があります。

変数のスコープは、グローバル変数とローカル変数の2種類にすぎません。

JavaScript 言語の特別な点は、グローバル変数を関数内で直接読み取ることができることです。


Jsコード

var n=999;

function f1(){
alert(n);
}

f1(); 一方で, 当然のことですが、関数内のローカル変数は関数外から読み込むことはできません。 Jsコード

function f1(){

var n=999;

}



alert(n);

// error

内部で変数を宣言する際に注意する箇所があります関数を使用するには、必ず var コマンドを使用してください。これを使用しない場合、実際にはグローバル変数を宣言していることになります。 Jsコード

function f1(){

n=999;

}



f1();

alert(n);

// 999

------ -------------------------------------------------- --------------------------------------

2.外部からの変数?

さまざまな理由により、関数内でローカル変数を取得する必要がある場合があります。ただし、前述したように、これは通常の状況では不可能であり、回避策によってのみ実現できます。 それは、関数の中に別の関数を定義することです。

Jsコード

function f1(){

n=999;

function f2(){ alert(n);

// 999

}

}

上記のコードでは、関数 f2 が関数 f1 の中に含まれています。この時点で、f1 内のすべてのローカル変数が f2 から見えます。ただし、その逆は機能しません。f2 内のローカル変数は f1 には見えません。これは、JavaScript 言語の独自の「チェーン スコープ」構造です。子オブジェクトは、すべての親オブジェクトの変数をレベルごとに検索します。したがって、親オブジェクトのすべての変数は子オブジェクトから見えますが、その逆はありません。

f2 は f1 のローカル変数を読み取ることができるので、f2 が戻り値として使用される限り、f1 の外部にあるその内部変数を読み取ることはできません!

Jsコード

function f1(){

n=999;

function f2(){ alert(n);

}



return f2;

}

var result=f1();

result(); // 999

---------------------- ---------------------------------------------------- --------- ----------------------------------

3. クロージャの概念

前のセクションのコードの f2 関数はクロージャです。 さまざまな専門文献における「閉鎖」の定義は非常に抽象的で、理解するのが困難です。私の理解では、クロージャは他の関数の内部変数を読み取ることができる関数です。

JavaScript言語ではローカル変数を読み込めるのは関数内のサブ関数のみなので、クロージャは単純に「関数内で定義された関数」と理解できます。

つまり、本質的に、クロージャは関数の内部と関数の外部を接続する橋です。

------------------------------------------------ -------------------------------------------------- ------b

4. 閉鎖の目的

クロージャーはさまざまな場所で使用できます。これには 2 つの大きな用途があります。1 つは、前述したように関数内の変数を読み取ることです。もう 1 つは、これらの変数の値をメモリに保持することです。 この文はどうやって理解できますか?以下のコードを見てください。

Jsコード

function f1(){

var n=999;

nAdd=function(){n+=1}

function f2() alえーっと(n) ;

}



return f2;

}

var result=f1();

result();

結果(); // 1000

このコードでは、result は実際にはクロージャ f2 関数です。これは 2 回実行され、1 回目の値は 999、2 回目の値は 1000 でした。これは、関数 f1 のローカル変数 n が常にメモリに格納され、f1 が呼び出された後に自動的にクリアされないことを証明します。 なぜですか?その理由は、f1 が f2 の親関数であり、f2 がグローバル変数に割り当てられているため、f2 は常にメモリ内に存在し、f2 の存在は f1 に依存するため、f1 は常にメモリ内にあり、削除されません。呼び出しが完了すると、ガベージ コレクション メカニズム (ガベージ コレクション) によってリサイクルされます。

このコードでもう 1 つ注目すべき点は、「nAdd=function(){n+=1}」という行です。まず、var キーワードが nAdd の前に使用されていないため、nAdd はローカル変数ではなくグローバル変数です。 。第二に、nAdd の値は匿名関数であり、この 匿名関数自体もクロージャであるため、nAdd は関数の外部で関数内のローカル変数を操作できるセッターと同等です。 ------------------------------------------------ -------------------------------------------------- ------

5. クロージャを使用する際の注意点

1) クロージャは関数内の変数をメモリに格納し、大量のメモリを消費するため、クロージャを悪用することはできません。そうしないと、Web ページでパフォーマンスの問題が発生し、IE でメモリ リークが発生する可能性があります。解決策は、関数を終了する前に、未使用のローカル変数をすべて削除することです。

2) クロージャは、親関数内の変数の値を親関数の外に変更します。したがって、親関数をオブジェクトとして使用し、クロージャをパブリック メソッドとして使用し、内部変数をプライベート値として使用する場合は、親関数内の変数の値を自由に

変更しないように注意する必要があります。 ------------------------------------------------ -------------------------------------------------- ------

6. 考える質問

次のコードの実行結果を理解できれば、クロージャの動作メカニズムを理解できるはずです。

Jsコード

var name = "The Window"; var object = { name : "My Object", getNameFunc: function(){ return function(){

return this.name;

};

}

};
alert(object.getNameFunc()());
//ウィンドウ



---------- ----- -------------------------------------- ----- ----------------------------------



JavaScript クロージャーの例

function innerFun() { var a=0;
function innerFun()
{ a++;alert(a);} }

innerFun()


上記のコードは
スコープが間違っています。 innerFun() の部分は、outerFun() の内側にあり、outerFun() の外側で呼び出すのは間違いです。

クロージャである次のように変更します:

Js コード

function innerFun()
{
var a=0;
function innerFun()
{
a++;
alert(a);
}
return innerFun //ここに注意してください
}
var obj=outerFun; ( );
obj(); //結果は 1
obj(); //結果は 2
var obj2=outerFun();
obj2(); obj2() ;
//結果は 2 です
クロージャとは:

内部関数が定義されているスコープの外で参照されると、内部関数のクロージャが作成されます。関数がスコープ関数変数の外で参照されています。外部関数呼び出しが完了しても、これらの変数はクロージャで必要とされるため、メモリ内で解放されません。

----------------- ----- -------------------------------------- ----- --------------------------------

別の例を見てみましょう JSコード

function innerFun()

{ var a =0;
alert(a);
}
var a=4;
outerFun();
alert(a);

結果は

0です,4

. 関数内では、 inside outFun() のスコープを維持するために var キーワードが内部的に使用されているためです。 /No var a =0;

alert(a);

}

var a=4;

outerFun();
alert(a);
結果は
0,0 です
本当に不思議です、なぜですか? スコープ チェーンはパスを記述します。用語の観点からは、変数の値はこのパスに沿って決定できます。 a=0 が実行されると、var キーワードが使用されないため、代入操作はスコープ チェーンに従って var a になります。 =4; 値を変更します


-------------------------------------- ----------- -------------------------------------- ----------- -------------------------------------- -----------


JavaScript クロージャがよくわからない場合は、以下に転載された記事をお読みください: (転載: http://www.felixwoo.com/archives/247) )

1. クロージャとは何ですか?

公式の説明は次のとおりです: クロージャーは多くの変数とこれらの変数にバインドされた環境を持つ式 (通常は関数) であり、これらの変数も式の一部です
理解できる人はほとんどいないと思います。彼の説明は学術的すぎるため、実際にはこの文は次のとおりです:
JavaScript のすべての関数はクロージャです

しかし、一般的に言えば、入れ子になった関数はより強力であり、私たちが「クロージャ」と呼んでいるもののほとんどです。次のコードを見てください:

function a() { 
 var i = 0; 
 function b() { alert(++i); } 
 return b;}var c = a();c();
このコードには 2 つの特徴があります: 1. 関数 b は関数 a 内にネストされています 2. 関数 a は関数 b を返します 図:


このように、var c=a() を実行すると、変数 c は実際には関数 b を指し、c() を実行すると、i の値を表示するウィンドウがポップアップします (1 回目)このコードは実際にクロージャを作成します。なぜなら、関数 a の外部の変数 c が関数 a の内部で関数 b を参照しているからです。つまり、関数 a の内部関数 b が a によって参照されているからです。関数aの外に変数を追加すると、クロージャが作成されます

コンストラクタ内のいわゆる「クロージャ」についてもう少し詳しく説明しましょう。ターゲットオブジェクトのメソッド関数として本体に別の関数が定義されており、メソッドです。このオブジェクトの関数は、外側の関数本体の一時変数を参照します。これにより、ターゲット オブジェクトが存続期間中常にそのメソッドを維持できる限り、元のメソッドを間接的に維持できます。このとき、最初のコンストラクターの呼び出しは終了し、一時変数の名前は消えていますが、変数の値は常に対象オブジェクトのメソッド内で参照でき、その値はまだ存在しています。同じコンストラクターが再度呼び出された場合でも、新しいオブジェクトとメソッドのみが生成され、新しい一時変数は最後の呼び出しとは独立した新しい値にのみ対応します。クロージャの機能

要するに、クロージャの機能は、a が実行されて返された後に、JavaScript のガベージ コレクション メカニズム GC が a の内部関数 b の実行を再利用するのを防ぐことです。これは、クロージャの機能についての非常に簡単な説明です。専門的でも厳密でもありませんが、一般的な意味は、クロージャを理解するには段階的なプロセスが必要であるということです。

在上面的例子中,由于闭包的存在使得函数a返回后,a中的i始终存在,这样每次执行c(),i都是自加1后alert出i的值。

  那 么我们来想象另一种情况,如果a返回的不是函数b,情况就完全不同了。因为a执行完后,b没有被返回给a的外界,只是被a所引用,而此时a也只会被b引 用,因此函数a和b互相引用但又不被外界打扰(被外界引用),函数a和b就会被GC回收。(关于Javascript的垃圾回收机制将在后面详细介绍)

三、闭包内的微观世界

  如果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境(excution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。

  1. 定义函数a的时候,js解释器会将函数a的作用域链(scope chain)设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope chain中只有window对象。

  2. 执行函数a的时候,a会进入相应的执行环境(excution context)

  3. 在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope chain。即a.scope=a的作用域链。

  4. 然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。

  5. 下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。

  6. 最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。

到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的作用域链包含了对函数a的活动对象的引用,也就是说b可以访问到a中定义的所有变量和函数。函数b被c引用,函数b又依赖函数a,因此函数a在返回后不会被GC回收。

当函数b执行的时候亦会像以上步骤一样。因此,执行时b的作用域链包含了3个对象:b的活动对象、a的活动对象和window对象,如下图所示:

如图所示,当在函数b中访问一个变量的时候,搜索顺序是:

  1. 先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依次查找,直到找到为止。

  2. 如果函数b存在prototype原型对象,则在查找完自身的活动对象后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。

  3. 如果整个作用域链上都无法找到,则返回undefined。

小结,本段中提到了两个重要的词语:函数的定义执行。文中提到函数的作用域是在定义函数时候就已经确定,而不是在执行的时候确定(参看步骤1和3)。用一段代码来说明这个问题:

function f(x) { 
  var g = function () { return x; }  return g;}var h = f(1);alert(h());

这段代码中变量h指向了f中的那个匿名函数(由g返回)。

  • 假设函数h的作用域是在执行alert(h())确定的,那么此时h的作用域链是:h的活动对象->alert的活动对象->window对象。

  • 假设函数h的作用域是在定义时确定的,就是说h指向的那个匿名函数在定义的时候就已经确定了作用域。那么在执行的时候,h的作用域链为:h的活动对象->f的活动对象->window对象。

如果第一种假设成立,那输出值就是undefined;如果第二种假设成立,输出值则为1。

运行结果证明了第2个假设是正确的,说明函数的作用域确实是在定义这个函数的时候就已经确定了。

四、闭包的应用场景
保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。

  1. 変数をメモリ内に維持します。前の例と同様に、クロージャにより、関数 a の i は常にメモリ内に存在するため、c() が実行されるたびに、i は 1 ずつインクリメントされます。

  2. 変数のセキュリティを保護して JS のプライベート プロパティとプライベート メソッドを実装します (外部からアクセスできません)
    プライベート プロパティとメソッドは Constructor の外部からアクセスできません

    function Constructor(... ) {
    var that = this;
    var メンバー名 = value;
    機能メンバー名(... ) {...}
    }

上記の 3 点はクロージャの最も基本的な適用シナリオであり、多くの古典的なケースはこれに由来しています。



5. Javascript のガベージ コレクション メカニズム

Javascript では、オブジェクトが参照されなくなった場合、そのオブジェクトは GC によってリサイクルされます。 2 つのオブジェクトが相互に参照し、サードパーティによって参照されなくなった場合、相互に参照している 2 つのオブジェクトもリサイクルされます。関数 a は b によって参照され、b は a の外側の c によって参照されるため、関数 a は実行後にリサイクルされません。

JavaScript クロージャーを理解することは、上級 JS プログラマーになる唯一の方法であり、その解釈と操作メカニズムを理解することによってのみ、より安全でエレガントなコードを書くことができます。上記の内容は JavaScript クロージャについて詳しく説明したものであり、皆様のお役に立てれば幸いです。

関連する推奨事項:

JSクロージャの一般的な形式の詳細な説明

古典的なJSクロージャの詳細な説明

JSクロージャの詳細な説明_基礎知識

以上がJavaScript クロージャーについての深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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