ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScriptのクロージャ機構を理解して使いこなす_基礎知識

JavaScriptのクロージャ機構を理解して使いこなす_基礎知識

WBOY
WBOYオリジナル
2016-05-16 15:45:12878ブラウズ

偉大な同志アインシュタインはかつてこう言いました。「6 歳の子供に何かを明確に説明できないのは、あなた自身がそれを理解していないということです。」しかし、27歳の友人にクロージャとは何かを説明したところ、完全に失敗してしまいました。

これはもともと、JavaScript のクロージャーについて Stack Overflow で外国人の友人が提起した質問でした。ただし、この質問は Stack Overflow で行われたものであるため、当然のことながら、次のような古典的な回答も多くあります。

外部関数内で内部関数、つまりネストされた関数を定義すると、内部関数も外部関数の変数にアクセスできます。

function foo(x) {
 var tmp = 3;
 function bar(y) {
 alert(x + y + (++tmp));
 }
 bar(10);
}

foo(2); // alert 16
foo(2); // alert 16
foo(2); // alert 16

このコードは正しく実行され、結果 16 が返されます。これは、bar が外部関数の変数 tmp にアクセスでき、外部関数 foo のパラメータ x にもアクセスできるためです。しかし、上の例はクロージャではありません。

クロージャを実装するには、内部関数を外部関数の戻り値として返す必要があります。つまり、内部関数は、メモリ内でアクセスされた外部関数内のすべての変数をロックします。これらの変数は、次のように bar のメモリに常駐し、ガベージ コレクターによってリサイクルされません:

function foo(x) {
 var tmp = 3;
 return function (y) {
 alert(x + y + (++tmp));
 }
}
var bar = foo(2); // bar 现在是个闭包了
bar(10); // alert 16
bar(10); // alert 17
bar(10); // alert 18

上記のコードでは、bar が初めて実行されるときも、結果 16 が返されます。これは、bar は foo のスコープに直接存在しなくなりましたが、x と tmp に引き続きアクセスできるためです。つまり、tmp は bar のクロージャでロックされているため、bar が実行されるたびに tmp がインクリメントされるため、bar が 2 回目と 3 回目に実行されると、それぞれ 17 と 18 が返されます。

この例では、x は単なる純粋な値であり、foo が呼び出されるとき、値 x はパラメータとして foo にコピーされます。

しかし、JavaScript がオブジェクトを処理するときは常に参照を使用します。オブジェクトをパラメータとして foo を呼び出すと、foo に渡されるのは実際には元のオブジェクトへの参照になるため、元のオブジェクトも閉じられたことになります。 . 、次のように:

function foo(x) {
 var tmp = 3;
 return function (y) {
 alert(x + y + tmp++);
 x.memb = x.memb ? x.memb + 1 : 1;
 alert(x.memb);
 }
}
var age = new Number(2);
var bar = foo(age); // bar 现在是个闭包了
bar(10); // alert 15 1
bar(10); // alert 16 2
bar(10); // alert 17 3

予想どおり、bar(10) が実行されるたびに、tmp がインクリメントされるだけでなく、x.memb もインクリメントされます。これは、関数本体内の x と関数外の age が同じオブジェクトを参照しているためです。

http://stackoverflow.com/questions/111102/how-do-javascript-closures-work 経由

補足: 上記の例を通して、クロージャをより明確に理解できるはずです。理解できたと思われる場合は、次のコードの実行結果を推測してみてください:

function foo(x) {
 var tmp = 3;
 return function (y) {
 alert(x + y + tmp++);
 x.memb = x.memb ? x.memb + 1 : 1;
 alert(x.memb);
 }
}
var age = new Number(2);
var bar1 = foo(age); // bar1 现在是个闭包了
bar1(10); // alert 15 1
bar1(10); // alert 16 2
bar1(10); // alert 17 3

var bar2 = foo(age); // bar2 现在也是个闭包了
bar2(10); // alert ? ?
bar2(10); // alert ? ?
bar2(10); // alert ? ?

bar1(10); // alert ? ?
bar1(10); // alert ? ?
bar1(10); // alert ? ?

実際にクロージャを使用すると、非常にエレガントなデザインを作成でき、funarg で定義されたさまざまな計算方法をカスタマイズできます。以下は、並べ替え条件関数をパラメーターとして受け入れる配列並べ替えの例です。

[1, 2, 3].sort(function (a, b) {
 ... // 排序条件
});
同じ例では、配列の map メソッドが、関数で定義された条件に基づいて元の配列を新しい配列にマップします。

[1, 2, 3].map(function (element) {
 return element * 2;
}); // [2, 4, 6]
関数パラメータを使用すると、検索メソッドを簡単に実装し、無制限の検索条件をサポートできます:

someCollection.find(function (element) {
 return element.someProperty == 'searchCondition';
});
各配列要素に関数を適用する一般的な forEach メソッドなどのアプリケーション関数もあります。

[1, 2, 3].forEach(function (element) {
 if (element % 2 != 0) {
  alert(element);
 }
}); // 1, 3
ちなみに、関数オブジェクトの apply メソッドや call メソッドは、関数型プログラミングのアプリケーション関数としても使用できます。 ここでは、それらをアプリケーション関数、つまりパラメーターに適用される関数と考えます (apply ではパラメーター リスト、call では独立したパラメーターです)。

クロージャには別の非常に重要な用途があります - 遅延呼び出し:
(function () {
 alert([].join.call(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);

カプセル化スコープを作成してヘルパー オブジェクトを非表示にすることもできます:
var a = 10;
setTimeout(function () {
 alert(a); // 10, after one second
}, 1000);
还有回调函数:

//...
var x = 10;
// only for example
xmlHttpRequestObject.onreadystatechange = function () {
 // 当数据就绪的时候,才会调用;
 // 这里,不论是在哪个上下文中创建
 // 此时变量“x”的值已经存在了
 alert(x); // 10
};
//...

var foo = {};

// 初始化
(function (object) {

 var x = 10;

 object.getX = function _getX() {
  return x;
 };

})(foo);

alert(foo.getX()); // 获得闭包 "x" – 10

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