摘要:但是,必須強(qiáng)調(diào),閉包是一個(gè)運(yùn)行期概念。通過原型鏈可以實(shí)現(xiàn)繼承,而與閉包相關(guān)的就是作用域鏈。常理來(lái)說(shuō),一個(gè)函數(shù)執(zhí)行完畢,其執(zhí)行環(huán)境的作用域鏈會(huì)被銷毀。所以此時(shí),的作用域鏈雖然銷毀了,但是其活動(dòng)對(duì)象仍在內(nèi)存中。
學(xué)習(xí)Javascript閉包(Closure)
javascript的閉包
JavaScript 閉包深入理解(closure)
理解 Javascript 的閉包
JavaScript 中的閉包
看了《JS高級(jí)程序設(shè)計(jì)》和上面幾篇文章,小總結(jié)下JS閉包~
作為一個(gè)初學(xué)者,學(xué)習(xí)一個(gè)新的概念無(wú)非就是知道 “它是什么”、“什么原理”、“怎樣創(chuàng)建”、“有什么用”、“怎么用”,這篇文章就從這幾個(gè)角度介紹閉包,歡迎小伙伴們拍磚~
什么是閉包上面幾篇文章,每一篇對(duì)閉包的定義都不相同,其實(shí)這真是一個(gè)很難用語(yǔ)言完美描述出來(lái)的東西。個(gè)人更喜歡阮一峰寫的:
閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。
在JavaScript中,函數(shù)A內(nèi)部的局部變量不能被A以外的函數(shù)訪問到,只能被A內(nèi)部的子函數(shù)B訪問到,當(dāng)在函數(shù)A外調(diào)用函數(shù)B時(shí),便可通過B訪問A中局部變量,那么子函數(shù)B就是閉包。(注意,是子函數(shù)哦~)
這樣說(shuō)比較抽象,舉個(gè)栗子~
function outer() { var a = 100; // outer的局部變量 function inner() { // 閉包 console.log(a); } return inner; // 沒有這條語(yǔ)句,閉包起不到在outer外部訪問變量a的作用~ } console.log(a); // 在外部直接訪問a出錯(cuò),Uncaught ReferenceError: a is not defined var test = outer(); // outer運(yùn)行完返回inner,賦值給test test(); // 100,執(zhí)行test(),相當(dāng)于執(zhí)行inner(),這樣就可以訪問到outer內(nèi)部的a了
司徒正美在blog中從另一個(gè)角度解釋了閉包:
閉包的原理簡(jiǎn)單來(lái)說(shuō),閉包就是在另一個(gè)作用域中保存了一份它從上一級(jí)函數(shù)或作用域取得的變量(鍵值對(duì)),而這些鍵值對(duì)是不會(huì)隨上一級(jí)函數(shù)的執(zhí)行完成而銷毀。周愛民說(shuō)得更清楚,閉包就是“屬性表”,閉包就是一個(gè)數(shù)據(jù)塊,閉包就是一個(gè)存放著“Name=Value”的對(duì)照表。就這么簡(jiǎn)單。但是,必須強(qiáng)調(diào),閉包是一個(gè)運(yùn)行期概念。
說(shuō)原理可能有些大,但這部分確實(shí)是要從內(nèi)部機(jī)制的角度,解釋下“為什么上面的栗子中通過inner就可以在outer外部訪問outer內(nèi)部的a”~
個(gè)人認(rèn)為在JS中,有兩個(gè)鏈很重要:原型鏈 和 作用域鏈。通過 原型鏈 可以實(shí)現(xiàn)繼承,而與 閉包 相關(guān)的就是 作用域鏈。
還是上面的栗子,假設(shè)outer定義在全局作用域中
1 function outer() { 2 var a = 100; 3 function inner() { 4 console.log(a); 5 } 6 return inner; 7 } 8 var test = outer(); 9 test();
當(dāng)執(zhí)行到1處,outer函數(shù)定義,其作用域鏈中只有一個(gè)全局對(duì)象。
然后執(zhí)行8,運(yùn)行outer()。此時(shí),outer的作用域鏈中有兩個(gè)對(duì)象:outer的活動(dòng)對(duì)象-->全局對(duì)象。
運(yùn)行outer(),會(huì)執(zhí)行outer里面的3,定義inner(),此時(shí)inner的作用域鏈?zhǔn)牵?b>outer的活動(dòng)對(duì)象-->全局對(duì)象。
執(zhí)行9,其實(shí)就是執(zhí)行inner函數(shù),此時(shí)其作用域鏈?zhǔn)牵?b>inner的活動(dòng)對(duì)象-->outer的活動(dòng)對(duì)象-->全局對(duì)象。
因?yàn)閕nner的作用域鏈中有outer的活動(dòng)對(duì)象,所以它可以訪問到outer的局部變量a。
常理來(lái)說(shuō),一個(gè)函數(shù)執(zhí)行完畢,其執(zhí)行環(huán)境的作用域鏈會(huì)被銷毀。但是outer執(zhí)行完畢后,其活動(dòng)對(duì)象仍然保留在內(nèi)存中,因?yàn)閕nner的作用域鏈仍在引用著這個(gè)活動(dòng)對(duì)象。所以此時(shí),outer的作用域鏈雖然銷毀了,但是其活動(dòng)對(duì)象仍在內(nèi)存中。直到test執(zhí)行完畢,outer的活動(dòng)對(duì)象才被銷毀。
也正因?yàn)槿绱耍?strong>閉包只能取得包含函數(shù)中任何變量的最后一個(gè)值,即包含函數(shù)執(zhí)行完畢時(shí)變量的值。改改之前的栗子~
function outer() { var a = 100; function inner() { console.log(a); } a = a + 50; // 改變a的值 return inner; } var test = outer(); test(); // 150,取得的a是150,而不是100
正是因?yàn)樽饔糜蜴?,只能里面的訪問外面的,外面的不能訪問里面的。也是基于作用域鏈,聰明的大師們想出了閉包,使得外面的可以訪問里面的。掌握作用域鏈很關(guān)鍵啊~
創(chuàng)建閉包的方式創(chuàng)建閉包最常見的方式就是在一個(gè)函數(shù)里面創(chuàng)建一個(gè)函數(shù),之前舉得栗子就是。
司徒正美大神給出了3種閉包實(shí)現(xiàn):
with(obj){ //這里是對(duì)象閉包 }
(function(){ //函數(shù)閉包 })();
try{ //... } catch(e) { //catch閉包 但I(xiàn)E里不行 }閉包的作用
1. 在函數(shù)外面讀取函數(shù)的局部變量
前面一直在說(shuō)這個(gè)事兒~
2. 在內(nèi)存中維持一個(gè)變量
function outer() { var a = 100; function inner() { console.log(a++); } return inner; } var test = outer(); test(); // 100 test(); // 101 test(); // 102
栗子中a一直在內(nèi)存中,每執(zhí)行一次test(),輸出值加1。因?yàn)?b>test在全局執(zhí)行環(huán)境中,所以a一直在內(nèi)存中,如果test在其他執(zhí)行環(huán)境中,當(dāng)這個(gè)執(zhí)行執(zhí)行環(huán)境銷毀的時(shí)候,a就不會(huì)再在內(nèi)存中了。
3. 實(shí)現(xiàn)面向?qū)ο笾械膶?duì)象
javascript并沒有提供類這樣的機(jī)制,但是可以通過閉包來(lái)模擬類的機(jī)制。把父函數(shù)當(dāng)作對(duì)象(object)使用,把閉包當(dāng)作它的公用方法(Public Method),把內(nèi)部變量當(dāng)作它的私有屬性(private value)。
function Person(){ var name = "XiaoMing"; return { setName : function(theName){ name = theName; }, getName : function(){ return name; } }; } var person = new Person(); console.log(person.name); // undefined console.log(person.getName()); // XiaoMing幾個(gè)閉包示例
參考的這幾篇文章基本都給出了閉包示例,悄悄摘選司徒正美大神的幾個(gè)放到這里~
//*************閉包uniqueID************* uniqueID = (function(){ var id = 0; return function() { return id++; // 返回,自加 }; })(); document.writeln(uniqueID()); //0 document.writeln(uniqueID()); //1 document.writeln(uniqueID()); //2 document.writeln(uniqueID()); //3 document.writeln(uniqueID()); //4
//*************閉包階乘************* var a = (function(n) { if (n<1) { alert("invalid arguments"); return 0; } if(n==1) { return 1; } else { return n * arguments.callee(n-1); } })(4); document.writeln(a);
//***********實(shí)現(xiàn)面向?qū)ο笾械膶?duì)象*********** function Person(){ var name = "XiaoMing"; return { setName : function(theName){ name = theName; }, getName : function(){ return name; } }; } var person = new Person(); console.log(person.name); // undefined console.log(person.getName()); // XiaoMing
//***********避免 Lift 效應(yīng)*********** var tasks = []; for (var i = 0; i < 5; i++) { // 注意有一層額外的閉包 tasks[tasks.length] = (function (i) { return function () { console.log("Current cursor is at " + i); }; })(i); } var len = tasks.length; while (len--) { tasks[len](); }
運(yùn)行結(jié)果:
Current cursor is at 4 Current cursor is at 3 Current cursor is at 2 Current cursor is at 1 Current cursor is at 0
補(bǔ)充:Lift效應(yīng)
var tasks = []; for (var i = 0; i < 5; i++) { tasks[tasks.length] = function () { console.log("Current cursor is at " + i); }; } var len = tasks.length; while (len--) { tasks[len](); }
上面這段代碼輸出5次
Current cursor is at 5
這一現(xiàn)象稱為 Lift效應(yīng)。原因在于
在引用函數(shù)外部變量時(shí),函數(shù)執(zhí)行時(shí)外部變量的值由運(yùn)行時(shí)決定而非定義時(shí)
實(shí)際編程的時(shí)候,很容易遇見lift效應(yīng),加一層閉包就可以解決哦~
需注意的問題由于閉包會(huì)使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會(huì)造成網(wǎng)頁(yè)的性能問題,在IE中可能導(dǎo)致內(nèi)存泄露。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除。
如果有非常龐大的對(duì)象,且預(yù)計(jì)會(huì)在老舊的引擎中執(zhí)行,則使用閉包時(shí),注意將閉包不需要的對(duì)象置為空引用。
這是我對(duì)于閉包學(xué)習(xí)的總結(jié),錯(cuò)誤和不足歡迎大家指正~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/85744.html
摘要:閉包是怎么通過作用域鏈霸占更多內(nèi)存的本文是作者學(xué)習(xí)高級(jí)程序設(shè)計(jì)第一小節(jié)的一點(diǎn)個(gè)人理解,詳細(xì)教程請(qǐng)參考原教材。函數(shù)執(zhí)行過程創(chuàng)建了一個(gè)函數(shù)的活動(dòng)對(duì)象,作用域鏈的最前端指向這個(gè)對(duì)象。函數(shù)執(zhí)行完畢返回值后執(zhí)行環(huán)境作用域鏈和活動(dòng)對(duì)象一并銷毀。 JavaScript 閉包是怎么通過作用域鏈霸占更多內(nèi)存的? 本文是作者學(xué)習(xí)《JavaScript 高級(jí)程序設(shè)計(jì)》7.2第一小節(jié)的一點(diǎn)個(gè)人理解,詳細(xì)教程請(qǐng)...
摘要:一前言這個(gè)周末,注意力都在學(xué)習(xí)基礎(chǔ)知識(shí)上面,剛好看到了閉包這個(gè)神圣的東西,所以打算把這兩天學(xué)到的總結(jié)下來(lái),算是鞏固自己所學(xué)。因此要注意閉包的使用,否則會(huì)導(dǎo)致性能問題。五總結(jié)閉包的作用能夠讀取其他函數(shù)內(nèi)部變量。 # 一、前言 這個(gè)周末,注意力都在學(xué)習(xí)基礎(chǔ)Js知識(shí)上面,剛好看到了閉包這個(gè)神圣的東西,所以打算把這兩天學(xué)到的總結(jié)下來(lái),算是鞏固自己所學(xué)。也可能有些不正確的地方,也請(qǐng)大家看到了,麻...
摘要:遞歸閉包模仿塊級(jí)作用域私有變量小結(jié)在編程中,使用函數(shù)表達(dá)式可以無(wú)需對(duì)函數(shù)命名,從而實(shí)現(xiàn)動(dòng)態(tài)編程。匿名函數(shù)也稱為拉姆達(dá)函數(shù)。函數(shù)聲明要求有名字,但函數(shù)表達(dá)式不需要。中的函數(shù)表達(dá)式和閉包都是極其有用的特性,利用它們可以實(shí)現(xiàn)很多功能。 1、遞歸 2、閉包 3、模仿塊級(jí)作用域 4、私有變量 5、小結(jié) 在JavaScript編程中,使用函數(shù)表達(dá)式可以無(wú)需對(duì)函數(shù)命名,從而實(shí)現(xiàn)動(dòng)態(tài)編程。匿名函數(shù)也稱...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。異步編程入門的全稱是前端經(jīng)典面試題從輸入到頁(yè)面加載發(fā)生了什么這是一篇開發(fā)的科普類文章,涉及到優(yōu)化等多個(gè)方面。 TypeScript 入門教程 從 JavaScript 程序員的角度總結(jié)思考,循序漸進(jìn)的理解 TypeScript。 網(wǎng)絡(luò)基礎(chǔ)知識(shí)之 HTTP 協(xié)議 詳細(xì)介紹 HTT...
摘要:申明與賦值立即執(zhí)行的函數(shù)表達(dá)式,通過創(chuàng)建一個(gè)函數(shù),并且立即執(zhí)行,來(lái)構(gòu)造一個(gè)新的域,從而控制的范圍。函數(shù)接受一個(gè)的形參,該參數(shù)是一個(gè)對(duì)象引用,并執(zhí)行了。在最新的標(biāo)準(zhǔn)中,引入了一個(gè)新概念。 筆記說(shuō)明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開的一個(gè)專欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過程的一些要點(diǎn)筆記以及感悟,完整的可以加入winter的專欄...
閱讀 3195·2021-11-10 11:35
閱讀 1303·2019-08-30 13:20
閱讀 1124·2019-08-29 16:18
閱讀 2137·2019-08-26 13:54
閱讀 2163·2019-08-26 13:50
閱讀 964·2019-08-26 13:39
閱讀 2480·2019-08-26 12:08
閱讀 1956·2019-08-26 10:37