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の外の変数から参照されるとクロージャが生成されます。
もっと徹底しましょう。いわゆる「クロージャ」とは、コンストラクタ本体内に別の関数を対象オブジェクトのメソッド関数として定義し、そのオブジェクトのメソッド関数が外側の関数本体内の一時変数を参照することです。これにより、ターゲット オブジェクトが存続期間中常にそのメソッドを維持できる限り、元のコンストラクター本体で使用される一時変数の値を間接的に維持できます。最初のコンストラクター呼び出しが終了し、一時変数の名前が消えていますが、変数の値はターゲット オブジェクトのメソッド内で常に参照でき、このメソッドを介してのみ値にアクセスできます。同じコンストラクターが再度呼び出された場合でも、新しいオブジェクトとメソッドのみが生成され、新しい一時変数は最後の呼び出しから独立した新しい値にのみ対応します。
2. クロージャの機能は何ですか?
つまり、クロージャの機能は、a が実行されて返された後、a の内部関数 b の実行は a に依存する必要があるため、JavaScript のガベージ コレクション メカニズム GC が a によって占有されているリソースを再利用するのをクロージャが阻止することです。の変数。これはクロージャの役割を非常に簡単に説明したものであり、専門的でも厳密でもありませんが、一般的な意味は、クロージャを理解するには段階的なプロセスが必要であるということです。上記の例では、クロージャの存在により、関数aが戻った後もaのiが常に存在します。このように、c()が実行されるたびに、iは追加後にアラートされるiの値になります。 1.
次に、a が関数 b 以外の値を返す場合、状況はまったく異なります。 a が実行された後、b は a の外に戻されるのではなく、a によってのみ参照されるため、関数 a と関数 b は相互に参照しますが、干渉されません。外部の世界によって (外部の世界によって参照される)、関数 a と b は GC によってリサイクルされます。 (JavaScriptのガベージコレクションの仕組みについては後ほど詳しく紹介します)
3. クロージャー内のミクロの世界
クロージャと関数 a と入れ子関数 b の関係をより深く理解したい場合は、関数実行コンテキスト (実行コンテキスト)、アクティブ オブジェクト (呼び出しオブジェクト)、スコープ (スコープ) など、他のいくつかの概念を導入する必要があります。 ) )、スコープチェーン。これらの概念を説明するために、関数 a の定義から実行までのプロセスを例として取り上げます。
関数bを実行すると上記と同じになります。したがって、次の図に示すように、実行中の b のスコープ チェーンには、b のアクティブ オブジェクト、a のアクティブ オブジェクト、ウィンドウ オブジェクトの 3 つのオブジェクトが含まれます。
如图所示,当在函数b中访问一个变量的时候,搜索顺序是:
小结,本段中提到了两个重要的词语:函数的定义与执行。文中提到函数的作用域是在定义函数时候就已经确定,而不是在执行的时候确定(参看步骤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的安全性。
在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。
通过保护变量的安全实现JS私有属性和私有方法(不能被外部访问)
私有属性和方法在Constructor外是无法被访问的
function Constructor(...) { var that = this; var membername = value; function membername(...) {...} }
以上3点是闭包最基本的应用场景,很多经典案例都源于此。
五、Javascript的垃圾回收机制
在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。
六、结语
理解了aScript的闭包的解释和运行机制才能写出更为安全和优雅的代码,希望对大家的学习有所帮助。