摘要:因為沒有塊級作用域,只有函數(shù)作用域,所以閉包的使用與函數(shù)是緊密相關(guān)的。模擬私有變量這里返回兩個閉包函數(shù)和。閉包會在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。
簡介
Javascript 中一個最重要的特性就是閉包的使用。因為閉包的使用,當前作用域總可以訪問外部的作用域。因為Javascript 沒有塊級作用域,只有函數(shù)作用域,所以閉包的使用與函數(shù)是緊密相關(guān)的。
各種專業(yè)文獻上的"閉包"(closure)定義非常抽象,很難看懂。我的理解是,閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。
由于在Javascript語言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,因此可以把閉包簡單理解成"定義在一個函數(shù)內(nèi)部的函數(shù)"。
閉包最大用處有兩個,一個是可以讀取函數(shù)內(nèi)部的變量,另一個就是讓這些變量的值始終保持在內(nèi)存中。
如下例子:
function f1(){ var n=999; nAdd=function(){n+=1} function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999 nAdd(); result(); // 1000
在這段代碼中,result實際上就是閉包f2函數(shù)。它一共運行了兩次,第一次的值是999,第二次的值是1000。這證明了,函數(shù)f1中的局部變量n一直保存在內(nèi)存中,并沒有在f1調(diào)用后被自動清除。
為什么會這樣呢?原因就在于f1是f2的父函數(shù),而f2被賦給了一個全局變量,這導致f2始終在內(nèi)存中,而f2的存在依賴于f1,因此f1也始終在內(nèi)存中,不會在調(diào)用結(jié)束后,被垃圾回收機制(garbage collection)回收。
這段代碼中另一個值得注意的地方,就是"nAdd=function(){n+=1}"這一行,首先在nAdd前面沒有使用var關(guān)鍵字,因此nAdd是一個全局變量,而不是局部變量。其次,nAdd的值是一個匿名函數(shù)(anonymous function),而這個匿名函數(shù)本身也是一個閉包,所以nAdd相當于是一個setter,可以在函數(shù)外部對函數(shù)內(nèi)部的局部變量進行操作。
模擬私有變量function Counter(start) { var count = start; return { increment: function() { count++; }, get: function() { return count; } } } var foo = Counter(4); foo.increment(); foo.get(); // 5
這里 Counter 返回兩個閉包:函數(shù) increment 和 get。這兩個函數(shù)一直保持著對 Counter 作用域的訪問,因此它們能一直訪問到定義在 Counter 作用域的變量 count。
私有變量的工作機制由于 Javascript 不可以對作用域賦值和引用,所以在上例中,是沒有辦法在外部直接訪問內(nèi)部私有變量 count。唯一的方法就是通過定義閉包來訪問。
var foo = new Counter(4); foo.hack = function() { count = 1337; };
上面的代碼不會改變 Counter 作用域內(nèi)的 count 變量值,因為 hack 沒有在 Counter 內(nèi)定義。上面這段代碼只會創(chuàng)建或者覆蓋全局變量 count。
循環(huán)內(nèi)的閉包一個最容易犯的錯誤就是在循環(huán)內(nèi)使用閉包。
for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 1000); }
上面這段代碼不會輸出0到9,而是連續(xù)輸出10次10。
上面的匿名會一直保持一個對變量 i 的引用。當調(diào)用 console.log 函數(shù)開始輸出時,這是循環(huán)已經(jīng)結(jié)束,而變量 i 已經(jīng)為10了。
為了避免上面的錯誤發(fā)生,我們需要在每次循環(huán)時為變量 i 值創(chuàng)建一個拷貝。
為了復制循環(huán)中變量的值,最好的方式是在外層加一個匿名的立刻執(zhí)行函數(shù)。
for(var i = 0; i < 10; i++) { (function(e) { setTimeout(function() { console.log(e); }, 1000); })(i); }
這個外部的匿名函數(shù)接收循環(huán)變量 i 作為第一個參數(shù),并將其值拷貝至它自身的參數(shù) e。
外部的匿名函數(shù)將參數(shù) e 再傳遞給 setTimeout,因此 setTimeout 有了指向參數(shù) e 的引用。而且這個參數(shù) e 的值不會因為外部的循環(huán)改變而改變。
還有另外一個方法可以實現(xiàn)同樣的效果,就是在 setTimeout 內(nèi)的匿名函數(shù)中再返回一個匿名函數(shù):
for(var i = 0; i < 10; i++) { setTimeout((function(e) { return function() { console.log(e); } })(i), 1000) }
此外,通過 bind 方法也可以實現(xiàn)。
for(var i = 0; i < 10; i++) { setTimeout(console.log.bind(console, i), 1000); }閉包的注意點
1)由于閉包會使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會造成網(wǎng)頁的性能問題,在IE中可能導致內(nèi)存泄露。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除。
2)閉包會在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。所以,如果你把父函數(shù)當作對象(object)使用,把閉包當作它的公用方法(Public Method),把內(nèi)部變量當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數(shù)內(nèi)部變量的值。
思考題如果你能理解下面兩段代碼的運行結(jié)果,應該就算理解閉包的運行機制了。
代碼片段一。
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()());
代碼片段二。
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name; }; } }; alert(object.getNameFunc()());參考地址
Closures and References
阮一峰的網(wǎng)絡日志:學習Javascript閉包(Closure)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/85999.html
摘要:系列目錄復習資料資料整理個人整理重溫基礎(chǔ)篇重溫基礎(chǔ)對象介紹重溫基礎(chǔ)對象介紹重溫基礎(chǔ)介紹重溫基礎(chǔ)相等性判斷本章節(jié)復習的是中的關(guān)于閉包,這個小哥哥呀,看看。這里隨著閉包函數(shù)的結(jié)束,執(zhí)行環(huán)境銷毀,變量回收。 本文是 重溫基礎(chǔ) 系列文章的第十九篇。今日感受:將混亂的事情找出之間的聯(lián)系,也是種能力。 系列目錄: 【復習資料】ES6/ES7/ES8/ES9資料整理(個人整理) 【重溫基礎(chǔ)】...
摘要:個人前端文章整理從最開始萌生寫文章的想法,到著手開始寫,再到現(xiàn)在已經(jīng)一年的時間了,由于工作比較忙,更新緩慢,后面還是會繼更新,現(xiàn)將已經(jīng)寫好的文章整理一個目錄,方便更多的小伙伴去學習。 showImg(https://segmentfault.com/img/remote/1460000017490740?w=1920&h=1080); 個人前端文章整理 從最開始萌生寫文章的想法,到著手...
摘要:內(nèi)存泄露內(nèi)存泄露概念在計算機科學中,內(nèi)存泄漏指由于疏忽或錯誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存。判斷內(nèi)存泄漏,以字段為準。 本文是 重溫基礎(chǔ) 系列文章的第二十二篇。 今日感受:優(yōu)化學習方法。 系列目錄: 【復習資料】ES6/ES7/ES8/ES9資料整理(個人整理) 【重溫基礎(chǔ)】1-14篇 【重溫基礎(chǔ)】15.JS對象介紹 【重溫基礎(chǔ)】16.JSON對象介紹 【重溫基礎(chǔ)】1...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關(guān)注公眾號也可以吧原理依賴收集源碼版之引用數(shù)據(jù)類型上 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學習吧研究基于...
閱讀 2918·2021-09-22 15:20
閱讀 2991·2021-09-22 15:19
閱讀 3506·2021-09-22 15:15
閱讀 2432·2021-09-08 09:35
閱讀 2408·2019-08-30 15:44
閱讀 3041·2019-08-30 10:50
閱讀 3791·2019-08-29 16:25
閱讀 1616·2019-08-26 13:55