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