一文搞懂JavaScript中最難理解概念之一的閉包
一、閉包的概念
當(dāng)通過(guò)調(diào)用外部函數(shù)返回的內(nèi)部函數(shù)后,即使外部函數(shù)已經(jīng)執(zhí)行結(jié)束了,但是內(nèi)部函數(shù)引用了外部函數(shù)的變量依然會(huì)保存在內(nèi)存中,我們把這些變量的集合,稱(chēng)為閉包(Closure)。
在了解閉包的概念和用途之前,理解作用域和變量的生命周期等基礎(chǔ)預(yù)備知識(shí),對(duì)于理解閉包非常有幫助。
二、怎么實(shí)現(xiàn)閉包
閉包是指一個(gè)函數(shù)可以訪問(wèn)它定義時(shí)所在的詞法作用域以及全局作用域中的變量。在JavaScript中,閉包可以通過(guò)函數(shù)嵌套和變量引用實(shí)現(xiàn)。
function outerFunction() { let outerVariable = '我在outer函數(shù)里!'; function innerFunction() { console.log(outerVariable); } return innerFunction; } const innerFunc = outerFunction(); innerFunc(); // 輸出: 我在outer函數(shù)里!
在上面的代碼示例中,innerFunction
引用了outerVariable
,因此JavaScript引擎會(huì)保留outerFunction
的作用域鏈,以便innerFunction
可以訪問(wèn)outerVariable
。
function a(){ function b(){ var bb = 888 console.log(aa); //輸出:666 } var aa = 666 return b } var demo = a() demo()
在上面的代碼示例中,a
函數(shù)定義了一個(gè)名為aa
的變量和一個(gè)名為b
的函數(shù),b
函數(shù)引用了aa
變量,因此JavaScript引擎會(huì)保留a
函數(shù)的作用域鏈,b
函數(shù)可以訪問(wèn)a
函數(shù)的執(zhí)行上下文,b
函數(shù)內(nèi)用到了外部函數(shù)a
的變量aa
,在a
函數(shù)調(diào)用結(jié)束后該函數(shù)執(zhí)行上下文會(huì)銷(xiāo)毀,但會(huì)保留一部分留在內(nèi)存中供b
函數(shù)使用,這就形成了閉包。
具體來(lái)說(shuō),當(dāng)內(nèi)部函數(shù)引用外部函數(shù)的變量時(shí),外部函數(shù)的作用域鏈將被保留在內(nèi)存中,以便內(nèi)部函數(shù)可以訪問(wèn)這些變量。 這種函數(shù)嵌套和變量共享的方式就是閉包的核心概念。當(dāng)一個(gè)函數(shù)返回另一個(gè)函數(shù)時(shí),它實(shí)際上返回了一個(gè)閉包,其中包含了原函數(shù)定義時(shí)的詞法作用域和相關(guān)變量。
三、閉包的用途
1.封裝私有變量
閉包可以用于封裝私有變量,以防止其被外部訪問(wèn)和修改。封裝私有變量可以一定程度上防止全局變量污染,使用閉包封裝私有變量可以將這些變量限制在函數(shù)內(nèi)部或模塊內(nèi)部,從而減少了全局變量的數(shù)量,降低了全局變量被誤用或意外修改的風(fēng)險(xiǎn)。
在下面這個(gè)例子中,調(diào)用函數(shù),輸出的結(jié)果都是1,但是顯然我們的代碼效果是想讓count
每次加一的。
function add() { let count = 0; count++; console.log(count); } add() //輸出1 add() //輸出1 add() //輸出1
一種顯而易見(jiàn)的方法是將count
提到函數(shù)體外,作為全局變量。這么做當(dāng)然是可以解決問(wèn)題,但是在實(shí)際開(kāi)發(fā)中,一個(gè)項(xiàng)目由多人共同開(kāi)發(fā),你不清楚別人定義的變量名稱(chēng)是什么,這么做有點(diǎn)冒險(xiǎn),有什么其他的辦法可以解決這個(gè)問(wèn)題呢?
function add(){ let count = 0 function a(){ count++ console.log(count); } return a } var res = add() res() //1 res() //2 res() //3
答案是用閉包。在上面的代碼示例中,add
函數(shù)返回了一個(gè)閉包a,其中包含了count
變量。由于count
只在add
函數(shù)內(nèi)部定義,因此外部無(wú)法直接訪問(wèn)它。但是,由于a
函數(shù)引用了count
變量,因此count
變量的值可以在閉包內(nèi)部被修改和訪問(wèn)。這種方式可以用于封裝一些私有的數(shù)據(jù)和邏輯。
2. 做緩存
函數(shù)一旦被執(zhí)行完畢,其內(nèi)存就會(huì)被銷(xiāo)毀,而閉包的存在,就可以保有內(nèi)部環(huán)境的作用域。
function foo(){ var myName ='張三' let test1 = 1 const test2 = 2 var innerBar={ getName: function(){ console.log(test1); return myName }, setName:function(newName){ myName = newName } } return innerBar } var bar = foo() console.log(bar.getName()); //輸出:1 張三 bar.setName('李四') console.log(bar.getName()); //輸出:2 李四
這里var bar = foo() 執(zhí)行完后本來(lái)應(yīng)該被銷(xiāo)毀,但是因?yàn)樾纬闪碎]包,所以導(dǎo)致foo執(zhí)行上下文沒(méi)有被銷(xiāo)毀干凈,被引用了的變量myName、test1沒(méi)被銷(xiāo)毀,閉包里存放的就是變量myName、test1,這個(gè)閉包就像是setName、getName的專(zhuān)屬背包,setName、getName依然可以使用foo執(zhí)行上下文中的test1和myName。
3. 模塊化編程(實(shí)現(xiàn)共有變量)
閉包還可以用于實(shí)現(xiàn)模塊化編程。模塊化編程是一種將程序拆分成小的、獨(dú)立的、可重用的模塊的編程風(fēng)格。閉包可以用于封裝模塊的私有變量和方法,以便防止其被外部訪問(wèn)和修改。例如:
const myModule = (function() { let privateVariable = '我是私有的!'; function privateMethod() { console.log(privateVariable); } return { publicMethod: function() { privateMethod(); } }; })(); myModule.publicMethod(); // 輸出: 我是私有的!
在上面的代碼示例中,myModule實(shí)際上是一個(gè)立即執(zhí)行的匿名函數(shù),它返回了一個(gè)包含publicMethod的對(duì)象。在函數(shù)內(nèi)部,定義了一個(gè)私有變量privateVariable和一個(gè)私有方法privateMethod。publicMethod是一個(gè)公共方法,它可以訪問(wèn)privateMethod,但是無(wú)法訪問(wèn)privateVariable。這種方式可以用于實(shí)現(xiàn)簡(jiǎn)單的模塊化編程。
四、閉包的缺點(diǎn)
閉包也存在著一個(gè)潛在的問(wèn)題,由于閉包會(huì)引用外部函數(shù)的變量,但是這些變量在外部函數(shù)執(zhí)行完畢后沒(méi)有被釋放,那么這些變量會(huì)一直存在于內(nèi)存中,總的內(nèi)存大小不變,但是可用內(nèi)存空間變小了。 一旦形成閉包,只有在頁(yè)面關(guān)閉后,閉包占用的內(nèi)存才會(huì)被回收,這就造成了所謂的內(nèi)存泄漏。
因此我們?cè)谑褂瞄]包時(shí)需要特別注意內(nèi)存泄漏的問(wèn)題,可以用以下兩種方法解決內(nèi)存泄露問(wèn)題:
1.及時(shí)釋放閉包:手動(dòng)調(diào)用閉包函數(shù),并將其返回值賦值為null,這樣可以讓閉包中的變量及時(shí)被垃圾回收器回收。
2.使用立即執(zhí)行函數(shù):在創(chuàng)建閉包時(shí),將需要保留的變量傳遞給一個(gè)立即執(zhí)行函數(shù),并將這些變量作為參數(shù)傳遞給閉包函數(shù),這樣可以保留所需的變量,而不會(huì)導(dǎo)致其他變量的內(nèi)存泄漏。
五、最后的話
總之,閉包是一種非常重要的編程技術(shù),可以讓程序員更加靈活地處理數(shù)據(jù)和邏輯。在實(shí)際的開(kāi)發(fā)過(guò)程中,合理地使用閉包可以幫助我們更加高效地編寫(xiě)代碼,提高程序的性能和可維護(hù)性。
到此這篇關(guān)于一文搞懂JavaScript中最難理解概念之一的閉包的文章就介紹到這了,更多相關(guān)JavaScript閉包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
添加一個(gè)以前寫(xiě)的table的spliter給大家
添加一個(gè)以前寫(xiě)的table的spliter給大家...2007-01-01Javascript的表單驗(yàn)證-揭開(kāi)正則表達(dá)式的面紗
Javascript的表單驗(yàn)證-揭開(kāi)正則表達(dá)式的面紗在本文重點(diǎn)介紹,感興趣的朋友一起學(xué)習(xí)吧2016-03-03bootstrap模態(tài)框消失問(wèn)題的解決方法
這篇文章主要為大家詳細(xì)整理了bootstrap模態(tài)框消失不消失各種問(wèn)題的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12JavaScript隨機(jī)生成信用卡卡號(hào)的方法
這篇文章主要介紹了JavaScript隨機(jī)生成信用卡卡號(hào)的方法,涉及javascript操作隨機(jī)隨機(jī)數(shù)生成信用卡卡號(hào)的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-04-04微信小程序之高德地圖多點(diǎn)路線規(guī)劃過(guò)程示例詳解
這篇文章主要介紹了微信小程序之高德地圖多點(diǎn)路線規(guī)劃過(guò)程示例詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01使用JavaScript進(jìn)行表單校驗(yàn)功能
最近在學(xué)習(xí)JavaScript,因此想到使用js實(shí)現(xiàn)表單校驗(yàn)。下面通過(guò)本文給大家分享使用javascript實(shí)現(xiàn)表單校驗(yàn)功能的步驟,需要的的朋友參考下吧2017-08-08