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