摘要:如果有時(shí)需要得到函數(shù)內(nèi)的局部變量。上面代碼中,函數(shù)就在函數(shù)內(nèi)部,這時(shí)內(nèi)部的所有局部變量,對(duì)都是可見(jiàn)的。所謂內(nèi)存泄漏指任何對(duì)象在您不再擁有或需要它之后仍然存在。閉包不能濫用,否則會(huì)導(dǎo)致內(nèi)存泄露,影響網(wǎng)頁(yè)的性能。
一、引子
閉包(closure)是 Javascript 語(yǔ)言的一個(gè)難點(diǎn),面試時(shí)常被問(wèn)及,也是它的特色,很多高級(jí)應(yīng)用都要依靠閉包實(shí)現(xiàn)。本文盡可能用簡(jiǎn)單易懂的話,講清楚閉包的概念、形成條件及其常見(jiàn)的面試題。
我們先來(lái)看一個(gè)例子:
var n = 999; function f1() { console.log(n); } f1() // 999
上面代碼中,函數(shù)f1可以讀取全局變量n。但是,函數(shù)外部無(wú)法讀取函數(shù)內(nèi)部聲明的變量。
function f1() { var n = 999; } console.log(n) // Uncaught ReferenceError: n is not defined
上面代碼中,函數(shù)f1內(nèi)部聲明的變量n,函數(shù)外是無(wú)法讀取的。
如果有時(shí)需要得到函數(shù)內(nèi)的局部變量。正常情況下,這是辦不到的,只有通過(guò)變通方法才能實(shí)現(xiàn)。那就是在函數(shù)的內(nèi)部,再定義一個(gè)函數(shù)。
function f1() { var n = 999; function f2() { console.log(n); // 999 } }
上面代碼中,函數(shù)f2就在函數(shù)f1內(nèi)部,這時(shí)f1內(nèi)部的所有局部變量,對(duì)f2都是可見(jiàn)的。既然f2可以讀取f1的局部變量,那么只要把f2作為返回值,我們不就可以在f1外部讀取它的內(nèi)部變量了嗎!
二、閉包是什么我們可以對(duì)上面代碼進(jìn)行如下修改:
function f1(){ var a = 999; function f2(){ console.log(a); } return f2; // f1返回了f2的引用 } var result = f1(); // result就是f2函數(shù)了 result(); // 執(zhí)行result,全局作用域下沒(méi)有a的定義, //但是函數(shù)閉包,能夠把定義函數(shù)的時(shí)候的作用域一起記住,輸出999
上面代碼中,函數(shù)f1的返回值就是函數(shù)f2,由于f2可以讀取f1的內(nèi)部變量,所以就可以在外部獲得f1的內(nèi)部變量了。
閉包就是函數(shù)f2,即能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。由于在JavaScript語(yǔ)言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取內(nèi)部變量,因此可以把閉包簡(jiǎn)單理解成“定義在一個(gè)函數(shù)內(nèi)部的函數(shù)”。閉包最大的特點(diǎn),就是它可以“記住”誕生的環(huán)境,比如f2記住了它誕生的環(huán)境f1,所以從f2可以得到f1的內(nèi)部變量。在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來(lái)的一座橋梁。
那到底什么是閉包呢?當(dāng)函數(shù)可以記住并訪問(wèn)所在的詞法作用域,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行,這就產(chǎn)生了閉包。 ----《你不知道的Javascript上卷》
我個(gè)人理解,閉包就是函數(shù)中的函數(shù)(其他語(yǔ)言不能函數(shù)再套函數(shù)),里面的函數(shù)可以訪問(wèn)外面函數(shù)的變量,外面的變量的是這個(gè)內(nèi)部函數(shù)的一部分。
閉包形成的條件函數(shù)嵌套
內(nèi)部函數(shù)引用外部函數(shù)的局部變量
三、閉包的特性每個(gè)函數(shù)都是閉包,每個(gè)函數(shù)天生都能夠記憶自己定義時(shí)所處的作用域環(huán)境。把一個(gè)函數(shù)從它定義的那個(gè)作用域,挪走,運(yùn)行。這個(gè)函數(shù)居然能夠記憶住定義時(shí)的那個(gè)作用域。不管函數(shù)走到哪里,定義時(shí)的作用域就帶到了哪里。接下來(lái)我們用兩個(gè)例子來(lái)說(shuō)明這個(gè)問(wèn)題:
//例題1 var inner; function outer(){ var a=250; inner=function(){ alert(a);//這個(gè)函數(shù)雖然在外面執(zhí)行,但能夠記憶住定義時(shí)的那個(gè)作用域,a是250 } } outer(); var a=300; inner();//一個(gè)函數(shù)在執(zhí)行的時(shí)候,找閉包里面的變量,不會(huì)理會(huì)當(dāng)前作用域。
//例題2 function outer(x){ function inner(y){ console.log(x+y); } return inner; } var inn=outer(3);//數(shù)字3傳入outer函數(shù)后,inner函數(shù)中x便會(huì)記住這個(gè)值 inn(5);//當(dāng)inner函數(shù)再傳入5的時(shí)候,只會(huì)對(duì)y賦值,所以最后彈出8四、閉包的內(nèi)存泄漏
棧內(nèi)存提供一個(gè)執(zhí)行環(huán)境,即作用域,包括全局作用域和私有作用域,那他們什么時(shí)候釋放內(nèi)存的?
全局作用域----只有當(dāng)頁(yè)面關(guān)閉的時(shí)候全局作用域才會(huì)銷毀
私有的作用域----只有函數(shù)執(zhí)行才會(huì)產(chǎn)生
一般情況下,函數(shù)執(zhí)行會(huì)形成一個(gè)新的私有的作用域,當(dāng)私有作用域中的代碼執(zhí)行完成后,我們當(dāng)前作用域都會(huì)主動(dòng)的進(jìn)行釋放和銷毀。但當(dāng)遇到函數(shù)執(zhí)行返回了一個(gè)引用數(shù)據(jù)類型的值,并且在函數(shù)的外面被一個(gè)其他的東西給接收了,這種情況下一般形成的私有作用域都不會(huì)銷毀。
如下面這種情況:
function fn(){ var num=100; return function(){ } } var f=fn();//fn執(zhí)行形成的這個(gè)私有的作用域就不能再銷毀了
也就是像上面這段代碼,fn函數(shù)內(nèi)部的私有作用域會(huì)被一直占用的,發(fā)生了內(nèi)存泄漏。所謂內(nèi)存泄漏指任何對(duì)象在您不再擁有或需要它之后仍然存在。閉包不能濫用,否則會(huì)導(dǎo)致內(nèi)存泄露,影響網(wǎng)頁(yè)的性能。閉包使用完了后,要立即釋放資源,將引用變量指向null。
接下來(lái)我們看下有關(guān)于內(nèi)存泄漏的一道經(jīng)典面試題:
function?outer(){ var?num=0;//內(nèi)部變量 return?function?add(){//通過(guò)return返回add函數(shù),就可以在outer函數(shù)外訪問(wèn)了 num++;//內(nèi)部函數(shù)有引用,作為add函數(shù)的一部分了 console.log(num); }; } var?func1=outer(); func1();//實(shí)際上是調(diào)用add函數(shù),?輸出1 func1();//輸出2 因?yàn)閛uter函數(shù)內(nèi)部的私有作用域會(huì)一直被占用 var?func2=outer(); func2();//?輸出1??每次重新引用函數(shù)的時(shí)候,閉包是全新的。 func2();//?輸出2??五、閉包的作用
1.可以讀取函數(shù)內(nèi)部的變量。
2.可以使變量的值長(zhǎng)期保存在內(nèi)存中,生命周期比較長(zhǎng)。因此不能濫用閉包,否則會(huì)造成網(wǎng)頁(yè)的性能問(wèn)題
3.可以用來(lái)實(shí)現(xiàn)JS模塊。
JS模塊:具有特定功能的js文件,將所有的數(shù)據(jù)和功能都封裝在一個(gè)函數(shù)內(nèi)部(私有的),只向外暴露一個(gè)包信n個(gè)方法的對(duì)象或函數(shù),模塊的使用者,只需要通過(guò)模塊暴露的對(duì)象調(diào)用方法來(lái)實(shí)現(xiàn)對(duì)應(yīng)的功能。
具體請(qǐng)看下面的例子:
//index.html文件
//myModule.js文件 (function () { var msg = "Beijing"http://私有數(shù)據(jù) //操作數(shù)據(jù)的函數(shù) function doSomething() { console.log("doSomething() "+msg.toUpperCase()) } function doOtherthing () { console.log("doOtherthing() "+msg.toLowerCase()) } //向外暴露對(duì)象(給外部使用的兩個(gè)方法) window.myModule2 = { doSomething: doSomething, doOtherthing: doOtherthing } })()六、閉包的運(yùn)用
我們要實(shí)現(xiàn)這樣的一個(gè)需求: 點(diǎn)擊某個(gè)按鈕, 提示"點(diǎn)擊的是第n個(gè)按鈕",此處我們先不用事件代理:
.....
萬(wàn)萬(wàn)沒(méi)想到,點(diǎn)擊任意一個(gè)按鈕,后臺(tái)都是彈出“第四個(gè)”,這是因?yàn)閕是全局變量,執(zhí)行到點(diǎn)擊事件時(shí),此時(shí)i的值為3。那該如何修改,最簡(jiǎn)單的是用let聲明i
for (let i = 0; i < btns.length; i++) { btns[i].onclick = function () { console.log("第" + (i + 1) + "個(gè)") } }
另外我們可以通過(guò)閉包的方式來(lái)修改:
for (var i = 0; i < btns.length; i++) { (function (j) { btns[j].onclick = function (i) { console.log("第" + (i + 1) + "個(gè)") } })(i) }
如果覺(jué)得文章對(duì)你有些許幫助,歡迎在我的GitHub博客點(diǎn)贊和關(guān)注,感激不盡!
ps:文章于2018.11.16重新修改,希望對(duì)你們有所收獲!
參考文章Javascript教程
你不知道的Javascript上卷
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/98323.html
摘要:深入系列第八篇,介紹理論上的閉包和實(shí)踐上的閉包,以及從作用域鏈的角度解析經(jīng)典的閉包題。定義對(duì)閉包的定義為閉包是指那些能夠訪問(wèn)自由變量的函數(shù)。 JavaScript深入系列第八篇,介紹理論上的閉包和實(shí)踐上的閉包,以及從作用域鏈的角度解析經(jīng)典的閉包題。 定義 MDN 對(duì)閉包的定義為: 閉包是指那些能夠訪問(wèn)自由變量的函數(shù)。 那什么是自由變量呢? 自由變量是指在函數(shù)中使用的,但既不是函數(shù)參數(shù)也...
摘要:使用上一篇文章的例子來(lái)說(shuō)明下自由變量進(jìn)階期深入淺出圖解作用域鏈和閉包訪問(wèn)外部的今天是今天是其中既不是參數(shù),也不是局部變量,所以是自由變量。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第7天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)階計(jì)...
摘要:閉包面試題解由于作用域鏈機(jī)制的影響,閉包只能取得內(nèi)部函數(shù)的最后一個(gè)值,這引起的一個(gè)副作用就是如果內(nèi)部函數(shù)在一個(gè)循環(huán)中,那么變量的值始終為最后一個(gè)值。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第8天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了...
摘要:理解閉包概念閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù)。閉包在執(zhí)行后,仍然可以訪問(wèn)內(nèi)部的,因?yàn)閷⒌膬?nèi)的活動(dòng)對(duì)象添加到了的作用域鏈。閉包的應(yīng)用監(jiān)聽(tīng)事件事件錯(cuò)誤的使用循環(huán)使用閉包封裝函數(shù),便于使用私有變量。 理解閉包 概念 閉包是指 有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的 函數(shù)。 函數(shù)式閉包(在內(nèi)部保存數(shù)據(jù)和對(duì)外部無(wú)副作用) 創(chuàng)建方法 在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù)(閉包) 原理 普通函數(shù):...
閱讀 5452·2021-09-22 15:50
閱讀 1937·2021-09-02 15:15
閱讀 1232·2019-08-29 12:49
閱讀 2605·2019-08-26 13:31
閱讀 3521·2019-08-26 12:09
閱讀 1276·2019-08-23 18:17
閱讀 2802·2019-08-23 17:56
閱讀 3003·2019-08-23 16:02