摘要:但是,不合理地濫用閉包,也會造成很多性能問題,從而使項(xiàng)目維護(hù)成本增加。
前言
相信很多小伙伴在工作或者面試過程中都遇到過這個(gè)問題,作為經(jīng)典的前端面試題之一,它高頻地出現(xiàn)在我們的求職生涯中。所以,了解和掌握它也就變得十分必要了
讀完這篇文章,你或許就會知道:
閉包是什么,它是怎么形成的
為什么要使用閉包
閉包會造成哪些問題
如果文章中有出現(xiàn)紕漏、錯(cuò)誤之處,還請看到的小伙伴多多指教,先行謝過
以下↓
作用域what? 不是在說閉包么,怎么又扯到作用域上面去了
稍安勿躁,在我們了解閉包之前,還是很有必要先了解一下 JavaScript 中的作用域
我們都知道在 JavaScript 中存在著全局變量和局部變量,全局變量可以在任何地方訪問到,然而局部變量只能在當(dāng)前作用域中訪問。全局作用域是不能直接訪問局部作用域中的變量,而局部作用域可以直接訪問全局作用域當(dāng)中的變量
就像一個(gè)代碼塊兒或函數(shù)被嵌套在另一個(gè)代碼塊兒或函數(shù)中一樣,作用域也會被嵌套在其他的作用域中。所以,如果在直接作用域中找不到一個(gè)變量的話,就會咨詢下一個(gè)外層作用域,如此繼續(xù)直到找到這個(gè)變量或者到達(dá)最外層作用域(也就是全局作用域)
說了這么多,其實(shí)說白了,所謂 作用域就是一組規(guī)則,它決定了一個(gè)變量(標(biāo)識符)在哪里和如何被查找
試想一下,現(xiàn)在有一個(gè)這樣的需求:我們想在全局作用域拿到局部作用域的某一個(gè)變量該怎么去做呢?
初識閉包在 JavaScript 中閉包無所不在,你只是必須認(rèn)出它并接納它
正常情況下,我們并不能拿到局部作用域的變量。但是,我們可以使用變通的方式:定義一個(gè)函數(shù)
讓我們看一下這段代碼
function foo() { var a = 2; function bar() { console.log( a ); // 2 } }
這樣在函數(shù) foo 中定義一個(gè) bar 函數(shù),在這個(gè)函數(shù)中我們就能訪問到定義在函數(shù) foo 中的變量 a 。既然我們這樣就可以訪問到 foo 函數(shù)里面的變量,那么,只要我們將 bar 這個(gè)函數(shù)作為返回值輸出,不就實(shí)現(xiàn)我們的需求了么? 是的!
function foo() { var a = 2; function bar() { console.log( a ); // 2 } return bar } var result = foo(); result() // 2
這就是閉包
讓我們來看看這個(gè)函數(shù)做了什么:
創(chuàng)建了一個(gè)函數(shù) foo
函數(shù)里面創(chuàng)建了一個(gè)變量 a 與函數(shù) bar
返回函數(shù) bar
現(xiàn)在,我們就對閉包有了一個(gè)基本的概念:定義在一個(gè)函數(shù)內(nèi)部的函數(shù)
再遇閉包詞法作用域:簡單理解為作用域是由編寫時(shí)函數(shù)被聲明的位置定義的
還是來看一下下面的代碼
function foo() { var a = 2; function baz() { console.log( a ); // 2 } bar( baz ); } function bar(fn) { fn(); }
與前面示例不同的是,這里我們并沒有將函數(shù) baz 返回,而是將它當(dāng)做值傳遞給 bar 這個(gè)函數(shù),然后在 bar 這個(gè)函數(shù)里面執(zhí)行,函數(shù) bar 保持了函數(shù) baz 的引用
相同的是,實(shí)際上函數(shù) baz 都在它被編寫時(shí)的詞法作用域之外被調(diào)用,bar() 依然擁有對那個(gè)作用域的引用,而這個(gè)引用稱為閉包
這就是閉包好了,看到這里是不是還有點(diǎn)懵呢,讓我們再來看一個(gè)更加常見的示例
for(var i = 1; i <= 5; i++) { setTimeout(function() { console.log(i) }, 1000) }
毫無疑問,運(yùn)行上面的代碼會輸出 5 個(gè) 6.很明顯,我們得到的結(jié)果是 i 在循環(huán)之后的最終值
那么,為什么會是這樣呢?
其實(shí),由于作用域的工作方式,我們在定時(shí)器函數(shù)中訪問到的 i 是共享到全局作用域的上的,它只有一個(gè),就是最終循環(huán)結(jié)束的值
想讓這個(gè)循環(huán)顯示我們想要的結(jié)果也很簡單,只需要這樣:
for(var i = 1; i <= 5; i++) { (function(j) { setTimeout(function() { console.log(j) }, 1000) })(i) }
使用一個(gè)立即執(zhí)行函數(shù)將定時(shí)器函數(shù)包裹起來,在這個(gè)函數(shù)中定義一個(gè)變量 j,然后將 i 當(dāng)做值傳遞進(jìn)去。這樣,在每次迭代的時(shí)候,變量 j 都會擁有 i 的一個(gè)拷貝,自然得到了我們想要的結(jié)果
同樣的,這也是一個(gè)閉包
當(dāng)然,我們也可以使用 ES6 中的 let 關(guān)鍵字聲明變量 i
通過前面的一些示例,我們不難發(fā)現(xiàn):閉包其實(shí)并沒有特定的格式,只要滿足一些條件,它就是閉包
所以:
閉包就是當(dāng)一個(gè)函數(shù)即使是在它的詞法作用域之外被調(diào)用時(shí),也可以記住并訪問它的詞法作用域
閉包的用途閉包最大的用途:
讀取函數(shù)內(nèi)部的變量
讓這些變量始終保存在內(nèi)存中
function f1(){ var n=999; nAdd=function(){n+=1} function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999 nAdd(); result(); // 1000
在這段代碼中,result 實(shí)際上就是閉包 f2 函數(shù)。它一共運(yùn)行了兩次,第一次的值是 999 ,第二次的值是 1000 。這證明了,函數(shù) f1 中的局部變量 n 一直保存在內(nèi)存中,并沒有在 f1 調(diào)用后被自動清除
使用閉包模擬私有方法(數(shù)據(jù)隱藏和封裝)
私有方法不僅僅有利于限制對代碼的訪問:還提供了管理全局命名空間的強(qiáng)大能力,避免非核心的方法弄亂了代碼的公共接口部分
var Counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } })();
這個(gè)環(huán)境中包含兩個(gè)私有項(xiàng):名為 privateCounter 的變量和名為 changeBy 的函數(shù)。這兩項(xiàng)都無法在這個(gè)匿名函數(shù)外部直接訪問。必須通過匿名函數(shù)返回的三個(gè)公共函數(shù)訪問。
這三個(gè)公共函數(shù)是共享同一個(gè)環(huán)境的閉包
閉包的問題閉包的用途在一定程度上也造成了很多問題,比如:閉包會使函數(shù)中的變量始終保存在內(nèi)存中,不能被 JavaScript 的垃圾回收清理,很容易造成內(nèi)存消耗過大,影響程序性能
后記閉包的合理運(yùn)用,會讓我們在開發(fā)中寫出更優(yōu)雅和干凈的代碼。但是,不合理地濫用閉包,也會造成很多性能問題,從而使項(xiàng)目維護(hù)成本增加。
所以,如何合理地使用這個(gè)有趣的東西,還需要我們多多鉆研和摸索,相信你一定可以對它越來越熟悉
最后,推薦一波前端學(xué)習(xí)歷程,不定期分享一些前端問題和有意思的東西歡迎 star 關(guān)注 傳送門
參考文檔You-Dont-Know-JS
閉包 - MDN
學(xué)習(xí)JavaScript閉包 - 阮一峰
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/104149.html
摘要:閉包引起的內(nèi)存泄漏總結(jié)從理論的角度將由于作用域鏈的特性中所有函數(shù)都是閉包但是從應(yīng)用的角度來說只有當(dāng)函數(shù)以返回值返回或者當(dāng)函數(shù)以參數(shù)形式使用或者當(dāng)函數(shù)中自由變量在函數(shù)外被引用時(shí)才能成為明確意義上的閉包。 文章同步到github js的閉包概念幾乎是任何面試官都會問的問題,最近把閉包這塊的概念梳理了一下,記錄成以下文章。 什么是閉包 我先列出一些官方及經(jīng)典書籍等書中給出的概念,這些概念雖然...
摘要:當(dāng)在中調(diào)用匿名函數(shù)時(shí),它們用的都是同一個(gè)閉包,而且在這個(gè)閉包中使用了和的當(dāng)前值的值為因?yàn)檠h(huán)已經(jīng)結(jié)束,的值為。最好將閉包當(dāng)作是一個(gè)函數(shù)的入口創(chuàng)建的,而局部變量是被添加進(jìn)這個(gè)閉包的。 閉包不是魔法 這篇文章使用一些簡單的代碼例子來解釋JavaScript閉包的概念,即使新手也可以輕松參透閉包的含義。 其實(shí)只要理解了核心概念,閉包并不是那么的難于理解。但是,網(wǎng)上充斥了太多學(xué)術(shù)性的文章,對于...
摘要:一言以蔽之,閉包,你就得掌握。當(dāng)函數(shù)記住并訪問所在的詞法作用域,閉包就產(chǎn)生了。所以閉包才會得以實(shí)現(xiàn)。從技術(shù)上講,這就是閉包。執(zhí)行后,他的內(nèi)部作用域并不會消失,函數(shù)依然保持有作用域的閉包。 網(wǎng)上總結(jié)閉包的文章已經(jīng)爛大街了,不敢說筆者這篇文章多么多么xxx,只是個(gè)人理解總結(jié)。各位看官瞅瞅就好,大神還希望多多指正。此篇文章總結(jié)與《JavaScript忍者秘籍》 《你不知道的JavaScri...
摘要:權(quán)威指南第版中閉包的定義函數(shù)對象可以通過作用域鏈相互關(guān)聯(lián)起來,函數(shù)體內(nèi)部的變量都可以保存在函數(shù)作用域內(nèi),這種特性在計(jì)算機(jī)科學(xué)文獻(xiàn)中成為閉包。循環(huán)中的閉包使用閉包時(shí)一種常見的錯(cuò)誤情況是循環(huán)中的閉包,很多初學(xué)者都遇到了這個(gè)問題。 閉包簡介 閉包是JavaScript的重要特性,那么什么是閉包? 《JavaScript高級程序設(shè)計(jì)(第3版)》中閉包的定義: 閉包就是指有權(quán)訪問另一個(gè)函數(shù)中的變...
摘要:注意由于閉包會額外的附帶函數(shù)的作用域內(nèi)部匿名函數(shù)攜帶外部函數(shù)的作用域,因此,閉包會比其它函數(shù)多占用些內(nèi)存空間,過度的使用可能會導(dǎo)致內(nèi)存占用的增加。 作用域和作用域鏈?zhǔn)莏avascript中非常重要的特性,對于他們的理解直接關(guān)系到對于整個(gè)javascript體系的理解,而閉包又是對作用域的延伸,也是在實(shí)際開發(fā)中經(jīng)常使用的一個(gè)特性,實(shí)際上,不僅僅是javascript,在很多語言中都...
摘要:也許最好的理解是閉包總是在進(jìn)入某個(gè)函數(shù)的時(shí)候被創(chuàng)建,而局部變量是被加入到這個(gè)閉包中。在函數(shù)內(nèi)部的函數(shù)的內(nèi)部聲明函數(shù)是可以的可以獲得不止一個(gè)層級的閉包。 前言 總括 :這篇文章使用有效的javascript代碼向程序員們解釋了閉包,大牛和功能型程序員請自行忽略。 譯者 :文章寫在2006年,可直到翻譯的21小時(shí)之前作者還在完善這篇文章,在Stackoverflow的How do Java...
閱讀 1368·2021-11-15 11:45
閱讀 3136·2021-09-27 13:36
閱讀 2880·2019-08-30 15:54
閱讀 997·2019-08-29 12:38
閱讀 2920·2019-08-29 11:22
閱讀 2998·2019-08-26 13:52
閱讀 2043·2019-08-26 13:30
閱讀 598·2019-08-26 10:37