偉大的愛因斯坦同志說過:“如果你無法向一個 6 歲小孩解釋清楚某問題,那說明你自己都沒整明白”。然而,當我向一個 27 歲的朋友解釋什么是閉包時,卻徹底失敗了。
這原本是國外某哥們兒在 Stack Overflow 上對 JavaScript 閉包所提出的問題。不過既然此問題是在 Stack Overflow 提出的,當然也會有很多高手出來解答,其中有些回答確實是經(jīng)典,如下面這個:
如果在一個外部函數(shù)中再定義一個內(nèi)部函數(shù),即函數(shù)嵌套函數(shù),那么內(nèi)部函數(shù)也可以訪問外部函數(shù)中的變量:
function foo(x) { var tmp = 3; function bar(y) { alert(x + y + (++tmp)); } bar(10);}foo(2); // alert 16foo(2); // alert 16foo(2); // alert 16
此段代碼可以正確執(zhí)行,并返回結(jié)果:16,因為 bar 能訪問外部函數(shù)的變量 tmp, 同時也能訪問外部函數(shù) foo 的參數(shù) x。但以上示例不是閉包!
要實現(xiàn)閉包的話,需要將內(nèi)部函數(shù)作為外部函數(shù)的返回值返回,內(nèi)部函數(shù)在返回前,會將所有已訪問過的外部函數(shù)中的變量在內(nèi)存中鎖定,也就是說,這些變量將常駐 bar 的內(nèi)存中,不會被垃圾回收器回收,如下:
function foo(x) { var tmp = 3; return function (y) { alert(x + y + (++tmp)); }}var bar = foo(2); // bar 現(xiàn)在是個閉包了bar(10); // alert 16bar(10); // alert 17bar(10); // alert 18
上述代碼中,第一次執(zhí)行 bar 時,仍會返回結(jié)果:16,因為 bar 仍然可以訪問 x 及 tmp,盡管它已經(jīng)不直接存在于 foo 的作用域內(nèi)。那么既然 tmp 被鎖定在 bar 的閉包里,那么每次執(zhí)行 bar 的時候,tmp 都會自增一次,所以第二次和第三次執(zhí)行 bar 時,分別返回 17 和 18。
此示例中,x 僅僅是個純粹的數(shù)值,當 foo 被調(diào)用時,數(shù)值 x 就會作為參數(shù)被拷貝至 foo 內(nèi)。
但是 JavaScript 處理對象的時候,使用的總是引用,如果用一個對象作為參數(shù)來調(diào)用 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 現(xiàn)在是個閉包了bar(10); // alert 15 1bar(10); // alert 16 2bar(10); // alert 17 3
和期望的一樣,每次執(zhí)行 bar(10) 時,不但 tmp 自增了,x.memb 也自增了,因為函數(shù)體內(nèi)的 x 和函數(shù)體外的 age 引用的是同一個對象。
via http://stackoverflow.com/questions/111102/how-do-javascript-closures-work
補充:通過以上示例,應該能比較清楚的理解閉包了。如果覺得自己理解了,可以試著猜猜下面這段代碼的執(zhí)行結(jié)果:
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 現(xiàn)在是個閉包了bar1(10); // alert 15 1bar1(10); // alert 16 2bar1(10); // alert 17 3var bar2 = foo(age); // bar2 現(xiàn)在也是個閉包了bar2(10); // alert ? ?bar2(10); // alert ? ?bar2(10); // alert ? ?bar1(10); // alert ? ?bar1(10); // alert ? ?bar1(10); // alert ? ?
實際使用的時候,閉包可以創(chuàng)建出非常優(yōu)雅的設計,允許對funarg上定義的多種計算方式進行定制。如下就是數(shù)組排序的例子,它接受一個排序條件函數(shù)作為參數(shù):
[1, 2, 3].sort(function (a, b) { ... // 排序條件});
同樣的例子還有,數(shù)組的map方法是根據(jù)函數(shù)中定義的條件將原數(shù)組映射到一個新的數(shù)組中:
[1, 2, 3].map(function (element) { return element * 2;}); // [2, 4, 6]
使用函數(shù)式參數(shù),可以很方便的實現(xiàn)一個搜索方法,并且可以支持無限制的搜索條件:
someCollection.find(function (element) { return element.someProperty == 'searchCondition';});
還有應用函數(shù),比如常見的forEach方法,將函數(shù)應用到每個數(shù)組元素:
[1, 2, 3].forEach(function (element) { if (element % 2 != 0) { alert(element); }}); // 1, 3
順便提下,函數(shù)對象的 apply 和 call方法,在函數(shù)式編程中也可以用作應用函數(shù)。 這里,我們將它們看作是應用函數(shù) ―― 應用到參數(shù)中的函數(shù)(在apply中是參數(shù)列表,在call中是獨立的參數(shù)):
(function () { alert([].join.call(arguments, ';')); // 1;2;3}).apply(this, [1, 2, 3]);
閉包還有另外一個非常重要的應用 ―― 延遲調(diào)用:
var a = 10;setTimeout(function () { alert(a); // 10, after one second}, 1000);還有回調(diào)函數(shù)://...var x = 10;// only for examplexmlHttpRequestObject.onreadystatechange = function () { // 當數(shù)據(jù)就緒的時候,才會調(diào)用; // 這里,不論是在哪個上下文中創(chuàng)建 // 此時變量“x”的值已經(jīng)存在了 alert(x); // 10};//...
還可以創(chuàng)建封裝的作用域來隱藏輔助對象:
var foo = {};// 初始化(function (object) { var x = 10; object.getX = function _getX() { return x; };})(foo);alert(foo.getX()); // 獲得閉包 "x" 主站蜘蛛池模板: 久久久一区二区精品 | 69性欧美高清影院 | 欧美高清一级片 | 免费一级毛片免费播放 | 国产精品久久久久久久久久三级 | 精品国产一区二区三 | 播色网 | 国产一国产精品一级毛片 | 操操插插| av在线浏览 | 最新福利在线 | 欧美高清第一页 | 中国hdxxxx护士爽在线观看 | 一级外国毛片 | 黄色一级片在线观看 | 久色精品视频 | 久久精品免费国产 | 国产精品久久久久久久久久大牛 | 中文字幕一区二区三区久久 | 日本成人一区二区三区 | 日本xxxx色视频在线观看免费, | a网站在线 | 国产亚洲综合精品 | 他也色在线视频 | 黄视频网站免费在线观看 | 欧美一级做a | 久久亚洲视频网 | 激情小说区| 久久久久久久久久美女 | 97超级碰碰人国产在线观看 | 精品国产一区二区三区在线 | 国产精品久久久久久久不卡 | 欧美 中文字幕 | 好吊色欧美一区二区三区四区 | 欧美亚洲国产日韩 | 看黄在线 | 国产一及毛片 | 狠狠干天天操 | 性欧美一区| 9191色| 成人免费毛片在线观看 |