理解和運(yùn)用JavaScript的閉包機(jī)制
偉大的愛因斯坦同志說過:“如果你無法向一個(gè) 6 歲小孩解釋清楚某問題,那說明你自己都沒整明白”。然而,當(dāng)我向一個(gè) 27 歲的朋友解釋什么是閉包時(shí),卻徹底失敗了。
這原本是國(guó)外某哥們兒在 Stack Overflow 上對(duì) JavaScript 閉包所提出的問題。不過既然此問題是在 Stack Overflow 提出的,當(dāng)然也會(huì)有很多高手出來解答,其中有些回答確實(shí)是經(jīng)典,如下面這個(gè):
如果在一個(gè)外部函數(shù)中再定義一個(gè)內(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 16 foo(2); // alert 16 foo(2); // alert 16
此段代碼可以正確執(zhí)行,并返回結(jié)果:16,因?yàn)?bar 能訪問外部函數(shù)的變量 tmp, 同時(shí)也能訪問外部函數(shù) foo 的參數(shù) x。但以上示例不是閉包!
要實(shí)現(xiàn)閉包的話,需要將內(nèi)部函數(shù)作為外部函數(shù)的返回值返回,內(nèi)部函數(shù)在返回前,會(huì)將所有已訪問過的外部函數(shù)中的變量在內(nèi)存中鎖定,也就是說,這些變量將常駐 bar 的內(nèi)存中,不會(huì)被垃圾回收器回收,如下:
function foo(x) { var tmp = 3; return function (y) { alert(x + y + (++tmp)); } } var bar = foo(2); // bar 現(xiàn)在是個(gè)閉包了 bar(10); // alert 16 bar(10); // alert 17 bar(10); // alert 18
上述代碼中,第一次執(zhí)行 bar 時(shí),仍會(huì)返回結(jié)果:16,因?yàn)?bar 仍然可以訪問 x 及 tmp,盡管它已經(jīng)不直接存在于 foo 的作用域內(nèi)。那么既然 tmp 被鎖定在 bar 的閉包里,那么每次執(zhí)行 bar 的時(shí)候,tmp 都會(huì)自增一次,所以第二次和第三次執(zhí)行 bar 時(shí),分別返回 17 和 18。
此示例中,x 僅僅是個(gè)純粹的數(shù)值,當(dāng) foo 被調(diào)用時(shí),數(shù)值 x 就會(huì)作為參數(shù)被拷貝至 foo 內(nèi)。
但是 JavaScript 處理對(duì)象的時(shí)候,使用的總是引用,如果用一個(gè)對(duì)象作為參數(shù)來調(diào)用 foo,那么 foo 中傳入的實(shí)際上是原始對(duì)象的引用,所以這個(gè)原始對(duì)象也相當(dāng)于被閉包了,如下:
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)在是個(gè)閉包了 bar(10); // alert 15 1 bar(10); // alert 16 2 bar(10); // alert 17 3
和期望的一樣,每次執(zhí)行 bar(10) 時(shí),不但 tmp 自增了,x.memb 也自增了,因?yàn)楹瘮?shù)體內(nèi)的 x 和函數(shù)體外的 age 引用的是同一個(gè)對(duì)象。
via http://stackoverflow.com/questions/111102/how-do-javascript-closures-work
補(bǔ)充:通過以上示例,應(yīng)該能比較清楚的理解閉包了。如果覺得自己理解了,可以試著猜猜下面這段代碼的執(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)在是個(gè)閉包了 bar1(10); // alert 15 1 bar1(10); // alert 16 2 bar1(10); // alert 17 3 var bar2 = foo(age); // bar2 現(xiàn)在也是個(gè)閉包了 bar2(10); // alert ? ? bar2(10); // alert ? ? bar2(10); // alert ? ? bar1(10); // alert ? ? bar1(10); // alert ? ? bar1(10); // alert ? ?
實(shí)際使用的時(shí)候,閉包可以創(chuàng)建出非常優(yōu)雅的設(shè)計(jì),允許對(duì)funarg上定義的多種計(jì)算方式進(jìn)行定制。如下就是數(shù)組排序的例子,它接受一個(gè)排序條件函數(shù)作為參數(shù):
[1, 2, 3].sort(function (a, b) { ... // 排序條件 });
同樣的例子還有,數(shù)組的map方法是根據(jù)函數(shù)中定義的條件將原數(shù)組映射到一個(gè)新的數(shù)組中:
[1, 2, 3].map(function (element) { return element * 2; }); // [2, 4, 6]
使用函數(shù)式參數(shù),可以很方便的實(shí)現(xiàn)一個(gè)搜索方法,并且可以支持無限制的搜索條件:
someCollection.find(function (element) { return element.someProperty == 'searchCondition'; });
還有應(yīng)用函數(shù),比如常見的forEach方法,將函數(shù)應(yīng)用到每個(gè)數(shù)組元素:
[1, 2, 3].forEach(function (element) { if (element % 2 != 0) { alert(element); } }); // 1, 3
順便提下,函數(shù)對(duì)象的 apply 和 call方法,在函數(shù)式編程中也可以用作應(yīng)用函數(shù)。 這里,我們將它們看作是應(yīng)用函數(shù) —— 應(yīng)用到參數(shù)中的函數(shù)(在apply中是參數(shù)列表,在call中是獨(dú)立的參數(shù)):
(function () { alert([].join.call(arguments, ';')); // 1;2;3 }).apply(this, [1, 2, 3]);
閉包還有另外一個(gè)非常重要的應(yīng)用 —— 延遲調(diào)用:
var a = 10; setTimeout(function () { alert(a); // 10, after one second }, 1000); 還有回調(diào)函數(shù): //... var x = 10; // only for example xmlHttpRequestObject.onreadystatechange = function () { // 當(dāng)數(shù)據(jù)就緒的時(shí)候,才會(huì)調(diào)用; // 這里,不論是在哪個(gè)上下文中創(chuàng)建 // 此時(shí)變量“x”的值已經(jīng)存在了 alert(x); // 10 }; //...
還可以創(chuàng)建封裝的作用域來隱藏輔助對(duì)象:
var foo = {}; // 初始化 (function (object) { var x = 10; object.getX = function _getX() { return x; }; })(foo); alert(foo.getX()); // 獲得閉包 "x" – 10
- 深入解析JavaScript的閉包機(jī)制
- javascript使用閉包模擬對(duì)象的私有屬性和方法
- JavaScript 閉包詳細(xì)介紹
- 幾句話帶你理解JS中的this、閉包、原型鏈
- 學(xué)習(xí)Javascript閉包(Closure)知識(shí)
- JavaScript中閉包的寫法和作用詳解
- Javascript閉包與函數(shù)柯里化淺析
- JS工作中的小貼士之”閉包“與事件委托的”阻止冒泡“
- JavaScript必知必會(huì)(九)function 說起 閉包問題
- JavaScript閉包實(shí)例詳解
- JS閉包、作用域鏈、垃圾回收、內(nèi)存泄露相關(guān)知識(shí)小結(jié)
- JavaScript 閉包機(jī)制詳解及實(shí)例代碼
相關(guān)文章
JavaScript中使用自然對(duì)數(shù)ln的方法
這篇文章主要介紹了JavaScript中使用自然對(duì)數(shù)ln的方法,是JS入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-06-06Javascript基礎(chǔ)教程之?dāng)?shù)據(jù)類型 (字符串 String)
javascript一共有9種數(shù)據(jù)類型,分別是字符串 String、數(shù)值型 Number、布爾型 Boolean、未定義 Undefine、空值 Null、對(duì)象 Object、引用Refernce、列表型 List、完成型 Completion,我們今天首先來看看(字符串 String)2015-01-01深入理解JavaScript系列(21):S.O.L.I.D五大原則之接口隔離原則ISP詳解
這篇文章主要介紹了深入理解JavaScript系列(21):S.O.L.I.D五大原則之接口隔離原則ISP詳解,本文講解了JavaScript接口、ISP與JavaScript、墮落的實(shí)現(xiàn)、靜態(tài)耦合、語義耦合、可擴(kuò)展性等內(nèi)容,需要的朋友可以參考下2015-03-03JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記10 再訪js對(duì)象
在ECMAScript中,兩個(gè)核心主題就是對(duì)象與函數(shù),而這兩個(gè)主題也有些互相纏繞的,在前面幾個(gè)博文中大略的過了一遍函數(shù)相關(guān)的基礎(chǔ)知識(shí),這篇文章再回到對(duì)象主題上來2012-10-10如何學(xué)習(xí)Javascript入門指導(dǎo)
首先要說明的是,咱現(xiàn)在不是高手,最多還是一個(gè)半桶水,算是入了JS的門2013-11-11關(guān)于JavaScript中string 的replace
在使用JavaScript對(duì)字符串進(jìn)行處理的時(shí)候我們經(jīng)常會(huì)用到replace方法,很簡(jiǎn)單的一個(gè)方法,以前一直不以為意,直到今天看JavaScript語言精粹的時(shí)候讀到了一個(gè)有趣的小例子的時(shí)候,并不是十分理解,了解了一下replace的用法才明白,原來replace不像想象中的那么簡(jiǎn)單2013-04-04