摘要:執(zhí)行返回的內(nèi)部函數(shù),依然能訪問變量輸出閉包中的作用域鏈理解作用域鏈對(duì)理解閉包也很有幫助。早期的版本里采用是計(jì)數(shù)的垃圾回收機(jī)制,閉包導(dǎo)致內(nèi)存泄露的一個(gè)原因就是這個(gè)算法的一個(gè)缺陷。
1. 什么是閉包?關(guān)于閉包,我翻了幾遍書,看了幾遍視頻,查了一些資料,可是還是迷迷糊糊的,干脆自己動(dòng)手來個(gè)總結(jié)吧 !歡迎指正... (~ o ~)~zZ
來看一些關(guān)于閉包的定義:
閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中變量的函數(shù) --《JS高級(jí)程序設(shè)計(jì)第三版》 p178
函數(shù)對(duì)象可以通過作用域鏈相關(guān)聯(lián)起來,函數(shù)體內(nèi)部的變量都可以保存在函數(shù)作用域內(nèi),這種特性稱為 ‘閉包’ 。 --《JS權(quán)威指南》 p183
內(nèi)部函數(shù)可以訪問定義它們的外部函數(shù)的參數(shù)和變量(除了this和arguments)。 --《JS語言精粹》 p36
來個(gè)定義總結(jié)
可以訪問外部函數(shù)作用域中變量的函數(shù)
被內(nèi)部函數(shù)訪問的外部函數(shù)的變量可以保存在外部函數(shù)作用域內(nèi)而不被回收---這是核心,后面我們遇到閉包都要想到,我們要重點(diǎn)關(guān)注被閉包引用的這個(gè)變量。
來創(chuàng)建個(gè)簡單的閉包
var sayName = function(){ var name = "jozo"; return function(){ alert(name); } }; var say = sayName(); say();
來解讀后面兩個(gè)語句:
var say = sayName() :返回了一個(gè)匿名的內(nèi)部函數(shù)保存在變量say中,并且引用了外部函數(shù)的變量name,由于垃圾回收機(jī)制,sayName函數(shù)執(zhí)行完畢后,變量name并沒有被銷毀。
say() :執(zhí)行返回的內(nèi)部函數(shù),依然能訪問變量name,輸出 "jozo" .
2. 閉包中的作用域鏈理解作用域鏈對(duì)理解閉包也很有幫助。
變量在作用域中的查找方式應(yīng)該都很熟悉了,其實(shí)這就是順著作用域鏈往上查找的。
當(dāng)函數(shù)被調(diào)用時(shí):
先創(chuàng)建一個(gè)執(zhí)行環(huán)境(execution context),及相應(yīng)的作用域鏈;
將arguments和其他命名參數(shù)的值添加到函數(shù)的活動(dòng)對(duì)象(activation object)
作用域鏈:當(dāng)前函數(shù)的活動(dòng)對(duì)象優(yōu)先級(jí)最高,外部函數(shù)的活動(dòng)對(duì)象次之,外部函數(shù)的外部函數(shù)的活動(dòng)對(duì)象依次遞減,直至作用域鏈的末端--全局作用域。優(yōu)先級(jí)就是變量查找的先后順序;
先來看個(gè)普通的作用域鏈:
function sayName(name){ return name; } var say = sayName("jozo");
這段代碼包含兩個(gè)作用域:a.全局作用域;b.sayName函數(shù)的作用域,也就是只有兩個(gè)變量對(duì)象,當(dāng)執(zhí)行到對(duì)應(yīng)的執(zhí)行環(huán)境時(shí),該變量對(duì)象會(huì)成為活動(dòng)對(duì)象,并被推入到執(zhí)行環(huán)境作用域鏈的前端,也就是成為優(yōu)先級(jí)最高的那個(gè)。 看圖說話:
這圖在JS高級(jí)程序設(shè)計(jì)書上也有,我重新繪了遍。
在創(chuàng)建sayName()函數(shù)時(shí),會(huì)創(chuàng)建一個(gè)預(yù)先包含變量對(duì)象的作用域鏈,也就是圖中索引為1的作用域鏈,并且被保存到內(nèi)部的[[Scope]]屬性中,當(dāng)調(diào)用sayName()函數(shù)的時(shí)候,會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境,然后通過復(fù)制函數(shù)的[[Scope]]屬性中的對(duì)象構(gòu)建起作用域鏈,此后,又有一個(gè)活動(dòng)對(duì)象(圖中索引為0)被創(chuàng)建,并被推入執(zhí)行環(huán)境作用域鏈的前端。
一般來說,當(dāng)函數(shù)執(zhí)行完畢后,局部活動(dòng)對(duì)象就會(huì)被銷毀,內(nèi)存中僅保存全局作用域。但是,閉包的情況又有所不同 :
再來看看看閉包的作用域鏈:
function sayName(name){ return function(){ return name; } } var say = sayName("jozo");
這個(gè)閉包實(shí)例比上一個(gè)例子多了一個(gè)匿名函數(shù)的作用域:
在匿名函數(shù)從sayName()函數(shù)中被返回后,它的作用域鏈被初始化為包含sayName()函數(shù)的活動(dòng)對(duì)象和全局變量對(duì)象。這樣,匿名函數(shù)就可以訪問在sayName()中定義的所有變量和參數(shù),更為重要的是,sayName()函數(shù)在執(zhí)行完畢后,其活動(dòng)對(duì)象也不會(huì)被銷毀,因?yàn)槟涿瘮?shù)的作用域鏈依然在引用這個(gè)活動(dòng)對(duì)象,換句話說,sayName()函數(shù)執(zhí)行完后,其執(zhí)行環(huán)境的作用域鏈會(huì)被銷毀,但他的活動(dòng)對(duì)象會(huì)留在內(nèi)存中,知道匿名函數(shù)會(huì)銷毀。這個(gè)也是后面要講到的內(nèi)存泄露的問題。
作用域鏈問題不寫那么多了,寫書上的東西也很累 o(╯□╰)o
3. 閉包的實(shí)例實(shí)例1:實(shí)現(xiàn)累加
// 方式1 var a = 0; var add = function(){ a++; console.log(a) } add(); add(); //方式2 :閉包 var add = (function(){ var a = 0; return function(){ a++; console.log(a); } })(); console.log(a); //undefined add(); add(); 相比之下方式2更加優(yōu)雅,也減少全局變量,將變量私有化
實(shí)例2 :給每個(gè)li添加點(diǎn)擊事件
var oli = document.getElementsByTagName("li"); var i; for(i = 0;i < 5;i++){ oli[i].onclick = function(){ alert(i); } } console.log(i); // 5 //執(zhí)行匿名函數(shù) (function(){ alert(i); //5 }());
上面是一個(gè)經(jīng)典的例子,我們都知道執(zhí)行結(jié)果是都彈出5,也知道可以用閉包解決這個(gè)問題,但是我剛開始始終不能明白為什么每次彈出都是5,為什么閉包可以解決這問題。后來捋一捋還是把它弄清晰了:
a. 先來分析沒用閉包前的情況:for循環(huán)中,我們給每個(gè)li點(diǎn)擊事件綁定了一個(gè)匿名函數(shù),匿名函數(shù)中返回了變量i的值,當(dāng)循環(huán)結(jié)束后,變量i的值變?yōu)?,此時(shí)我們?cè)偃c(diǎn)擊每個(gè)li,也就是執(zhí)行相應(yīng)的匿名函數(shù)(看上面的代碼),這是變量i已經(jīng)是5了,所以每個(gè)點(diǎn)擊彈出5. 因?yàn)檫@里返回的每個(gè)匿名函數(shù)都是引用了同一個(gè)變量i,如果我們新建一個(gè)變量保存循環(huán)執(zhí)行時(shí)當(dāng)前的i的值,然后再讓匿名函數(shù)應(yīng)用這個(gè)變量,最后再返回這個(gè)匿名函數(shù),這樣就可以達(dá)到我們的目的了,這就是運(yùn)用閉包來實(shí)現(xiàn)的!
b. 再來分析下運(yùn)用閉包時(shí)的情況:
var oli = document.getElementsByTagName("li"); var i; for(i = 0;i < 5;i++){ oli[i].onclick = (function(num){ var a = num; // 為了說明問題 return function(){ alert(a); } })(i) } console.log(i); // 5
這里for循環(huán)執(zhí)行時(shí),給點(diǎn)擊事件綁定的匿名函數(shù)傳遞i后立即執(zhí)行返回一個(gè)內(nèi)部的匿名函數(shù),因?yàn)閰?shù)是按值傳遞的,所以此時(shí)形參num保存的就是當(dāng)前i的值,然后賦值給局部變量 a,然后這個(gè)內(nèi)部的匿名函數(shù)一直保存著a的引用,也就是一直保存著當(dāng)前i的值。 所以循環(huán)執(zhí)行完畢后點(diǎn)擊每個(gè)li,返回的匿名函數(shù)執(zhí)行彈出各自保存的 a 的引用的值。
4. 閉包的運(yùn)用我們來看看閉包的用途。事實(shí)上,通過使用閉包,我們可以做很多事情。比如模擬面向?qū)ο蟮拇a風(fēng)格;更優(yōu)雅,更簡潔的表達(dá)出代碼;在某些方面提升代碼的執(zhí)行效率。
1. 匿名自執(zhí)行函數(shù)
我們?cè)趯?shí)際情況下經(jīng)常遇到這樣一種情況,即有的函數(shù)只需要執(zhí)行一次,其內(nèi)部變量無需維護(hù),比如UI的初始化,那么我們可以使用閉包:
//將全部li字體變?yōu)榧t色 (function(){ var els = document.getElementsByTagName("li"); for(var i = 0,lng = els.length;i < lng;i++){ els[i].style.color = "red"; } })();
我們創(chuàng)建了一個(gè)匿名的函數(shù),并立即執(zhí)行它,由于外部無法引用它內(nèi)部的變量,
因此els,i,lng這些局部變量在執(zhí)行完后很快就會(huì)被釋放,節(jié)省內(nèi)存!
關(guān)鍵是這種機(jī)制不會(huì)污染全局對(duì)象。
2. 實(shí)現(xiàn)封裝/模塊化代碼
var person= function(){ //變量作用域?yàn)楹瘮?shù)內(nèi)部,外部無法訪問 var name = "default"; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } }(); console.log(person.name);//直接訪問,結(jié)果為undefined console.log(person.getName()); //default person.setName("jozo"); console.log(person.getName()); //jozo
3. 實(shí)現(xiàn)面向?qū)ο笾械膶?duì)象
這樣不同的對(duì)象(類的實(shí)例)擁有獨(dú)立的成員及狀態(tài),互不干涉。雖然JavaScript中沒有類這樣的機(jī)制,但是通過使用閉包,
我們可以模擬出這樣的機(jī)制。還是以上邊的例子來講:
function Person(){ var name = "default"; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } }; var person1= Person(); print(person1.getName()); john.setName("person1"); print(person1.getName()); // person1 var person2= Person(); print(person2.getName()); jack.setName("erson2"); print(erson2.getName()); //person2
Person的兩個(gè)實(shí)例person1 和 person2 互不干擾!因?yàn)檫@兩個(gè)實(shí)例對(duì)name這個(gè)成員的訪問是獨(dú)立的 。
5. 內(nèi)存泄露及解決方案垃圾回收機(jī)制
說到內(nèi)存管理,自然離不開JS中的垃圾回收機(jī)制,有兩種策略來實(shí)現(xiàn)垃圾回收:標(biāo)記清除 和 引用計(jì)數(shù);
標(biāo)記清除:垃圾收集器在運(yùn)行的時(shí)候會(huì)給存儲(chǔ)在內(nèi)存中的所有變量都加上標(biāo)記,然后,它會(huì)去掉環(huán)境中的變量的標(biāo)記和被環(huán)境中的變量引用的變量的標(biāo)記,此后,如果變量再被標(biāo)記則表示此變量準(zhǔn)備被刪除。 2008年為止,IE,F(xiàn)irefox,opera,chrome,Safari的javascript都用使用了該方式;
引用計(jì)數(shù):跟蹤記錄每個(gè)值被引用的次數(shù),當(dāng)聲明一個(gè)變量并將一個(gè)引用類型的值賦給該變量時(shí),這個(gè)值的引用次數(shù)就是1,如果這個(gè)值再被賦值給另一個(gè)變量,則引用次數(shù)加1。相反,如果一個(gè)變量脫離了該值的引用,則該值引用次數(shù)減1,當(dāng)次數(shù)為0時(shí),就會(huì)等待垃圾收集器的回收。
這個(gè)方式存在一個(gè)比較大的問題就是循環(huán)引用,就是說A對(duì)象包含一個(gè)指向B的指針,對(duì)象B也包含一個(gè)指向A的引用。 這就可能造成大量內(nèi)存得不到回收(內(nèi)存泄露),因?yàn)樗鼈兊囊么螖?shù)永遠(yuǎn)不可能是 0 。早期的IE版本里(ie4-ie6)采用是計(jì)數(shù)的垃圾回收機(jī)制,閉包導(dǎo)致內(nèi)存泄露的一個(gè)原因就是這個(gè)算法的一個(gè)缺陷。
我們知道,IE中有一部分對(duì)象并不是原生額javascript對(duì)象,例如,BOM和DOM中的對(duì)象就是以COM對(duì)象的形式實(shí)現(xiàn)的,而COM對(duì)象的垃圾回收機(jī)制采用的就是引用計(jì)數(shù)。因此,雖然IE的javascript引擎采用的是標(biāo)記清除策略,但是訪問COM對(duì)象依然是基于引用計(jì)數(shù)的,因此只要在IE中設(shè)計(jì)COM對(duì)象就會(huì)存在循環(huán)引用的問題!
舉個(gè)栗子:
window.onload = function(){ var el = document.getElementById("id"); el.onclick = function(){ alert(el.id); } }
這段代碼為什么會(huì)造成內(nèi)存泄露?
el.onclick= function () { alert(el.id); };
執(zhí)行這段代碼的時(shí)候,將匿名函數(shù)對(duì)象賦值給el的onclick屬性;然后匿名函數(shù)內(nèi)部又引用了el對(duì)象,存在循環(huán)引用,所以不能被回收;
解決方法:
window.onload = function(){ var el = document.getElementById("id"); var id = el.id; //解除循環(huán)引用 el.onclick = function(){ alert(id); } el = null; // 將閉包引用的外部函數(shù)中活動(dòng)對(duì)象清除 }6. 總結(jié)閉包的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
可以讓一個(gè)變量常駐內(nèi)存 (如果用的多了就成了缺點(diǎn)
避免全局變量的污染
私有化變量
缺點(diǎn)
因?yàn)殚]包會(huì)攜帶包含它的函數(shù)的作用域,因此會(huì)比其他函數(shù)占用更多的內(nèi)存
引起內(nèi)存泄露
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/85707.html
摘要:該對(duì)象包含了函數(shù)的所有局部變量命名參數(shù)參數(shù)集合以及,然后此對(duì)象會(huì)被推入作用域鏈的前端。如果整個(gè)作用域鏈上都無法找到,則返回。此時(shí)的作用域鏈包含了兩個(gè)對(duì)象的活動(dòng)對(duì)象和對(duì)象。 前端學(xué)習(xí):教程&開發(fā)模塊化/規(guī)范化/工程化/優(yōu)化&工具/調(diào)試&值得關(guān)注的博客/Git&面試-前端資源匯總 歡迎提issues斧正:閉包 JavaScript-閉包 閉包(closure)是一個(gè)讓人又愛又恨的somet...
摘要:引擎對(duì)堆內(nèi)存中的對(duì)象進(jìn)行分代管理新生代存活周期較短的對(duì)象,如臨時(shí)變量字符串等。內(nèi)存泄漏對(duì)于持續(xù)運(yùn)行的服務(wù)進(jìn)程,必須及時(shí)釋放不再用到的內(nèi)存。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開始前端進(jìn)階的第一期,本周的主題是調(diào)用堆棧,今天是第4天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)階計(jì)劃...
摘要:不是引用類型,無法輸出簡而言之,堆內(nèi)存存放引用值,棧內(nèi)存存放固定類型值。變量的查詢?cè)谧兞康牟樵冎校L問局部變量要比全局變量來得快,因此不需要向上搜索作用域鏈。 贊助我以寫出更好的文章,give me a cup of coffee? 2017最新最全前端面試題 基本類型值有:undefined,NUll,Boolean,Number和String,這些類型分別在內(nèi)存中占有固定的大小空...
摘要:因?yàn)楫?dāng)文件放在頂部時(shí),頁面會(huì)逐步呈現(xiàn),有較好的用戶體驗(yàn),如果將文件放在底部,瀏覽器為了避免回流,會(huì)阻塞內(nèi)容的呈現(xiàn)。瀏覽器可能需要在本地存儲(chǔ)各種各樣的數(shù)據(jù),例如等。 瀏覽器是怎么渲染的? DOM樹+CSS規(guī)則樹—>渲染樹—調(diào)用系統(tǒng)GUI的API來繪制頁面 1)瀏覽器下載html文件之后,會(huì)根據(jù)html文件構(gòu)建DOM樹,其中css會(huì)構(gòu)建css規(guī)則樹,js會(huì)修改dom樹和css規(guī)則樹; ...
摘要:之前一篇文章我們?cè)敿?xì)說明了變量對(duì)象,而這里,我們將詳細(xì)說明作用域鏈。而的作用域鏈,則同時(shí)包含了這三個(gè)變量對(duì)象,所以的執(zhí)行上下文可如下表示。下圖展示了閉包的作用域鏈。其中為當(dāng)前的函數(shù)調(diào)用棧,為當(dāng)前正在被執(zhí)行的函數(shù)的作用域鏈,為當(dāng)前的局部變量。 showImg(https://segmentfault.com/img/remote/1460000008329355);初學(xué)JavaScrip...
閱讀 4015·2023-04-26 02:13
閱讀 2260·2021-11-08 13:13
閱讀 2748·2021-10-11 10:59
閱讀 1745·2021-09-03 00:23
閱讀 1314·2019-08-30 15:53
閱讀 2293·2019-08-28 18:22
閱讀 3061·2019-08-26 10:45
閱讀 744·2019-08-23 17:58