淺談 JavaScript 閉包

相信學習 JavaScript 的同學都知道「閉包(Closure)」,這個概念在 JavaScript 中是非常重要的,并且在大多數(shù)人看來閉包是非常難以理解的概念。既然這樣,那今天就帶大家一起來看看這到底是何方神圣。

維基百科是這樣解釋的:

閉包,又稱詞法閉包或函數(shù)閉包,是引用了自由變量的函數(shù),這個被引用的自由變量將和這個函數(shù)一同存在,即使離開了創(chuàng)造它的環(huán)境也不例外,所以,閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實體

說了半天一句也沒看懂,那我們來看看 JS 官方是怎么解釋的:

閉包是指多個變量和綁定了這些變量的環(huán)境的表達式( 通常是一個函數(shù) ),因而這些變量也是該表達式的一部分。

我去,這又是什么東西?只看出閉包是個函數(shù),其他的還是一概不知。

既然這樣不知道它們在說些什么,不如跟著我的思路來看一看到底是個什么。

要理解閉包,首先我們要弄明白什么是詞法作用域作用域鏈。

作用域一般有兩種常見的模型,一種叫做詞法作用域,另一種叫做動態(tài)作用域。我們的 JavaScript 就是基于詞法作用域的語言。

簡單來講,詞法作用域就是一個變量的作用在出生(定義)時就已經(jīng)被設(shè)定好了,當在本作用域中找不到變量時,就會一直向父作用域中去查找,直到直到為止。如果不明白的話,看下面的代碼大概就能理解了。

代碼中 fun1 在其內(nèi)部已經(jīng)定義了變量 y,所以在查找 y 時在該作用域(內(nèi)部函數(shù) fun1 中)內(nèi)可以找到,則無需再往父作用域中去查找;如果在其作用域內(nèi)沒有查找到,則會在父作用域內(nèi)查找,也就是使用 fun 函數(shù)中的變量 y。

既然 JavaScript 中的函數(shù)和變量都有其作用域,那么作用域之間就會產(chǎn)生一條鏈,我們稱之為作用域鏈。假設(shè)我們編寫了一段 JS 代碼,那這段代碼就會有一個與之關(guān)聯(lián)的作用域鏈。這個作用域鏈就是由全局對象(如:window)、我們自定義的對象(函數(shù),局部變量)組成。比如上面的代碼,其作用域鏈上是這樣的:函數(shù) fun1、變量 y ==> 函數(shù) fun、變量 x、y ==> 全局對象。這就是所謂的作用域鏈。

理解了上面的內(nèi)容,就可以來看看咱們今天的主人公「閉包」了。

函數(shù)對象可以通過作用域鏈相互關(guān)聯(lián)起來,函數(shù)體內(nèi)部的變量都可以保存在函數(shù)作用域內(nèi),也就是函數(shù)變量可以被藏在作用域鏈之內(nèi),這種特性在計算機科學文獻中稱為閉包??瓷先プ兞勘弧胺忾]包裹”了起來。由此可見,從理論上講,所有的 JavaScript 函數(shù)全都是閉包的,因為它們都是對象,它們都關(guān)聯(lián)在作用域鏈上。

那么怎么才能顯式的形成閉包呢?先來看下面的例子。

注意這段代碼中標記的地方:內(nèi)部函數(shù) fun1 在執(zhí)行前通過外部函數(shù)被返回了,外部函數(shù)被賦值給了變量 result。這時,變量 result 的值就變成了函數(shù) fun1,也就是說內(nèi)部變量 name 在所屬函數(shù)外部被調(diào)用了。我們來證實一下:

可以看到 result 的值就是函數(shù) fun1,那為什么還可以讀取變量 name 呢?答案就是 result 變成閉包了。

result 由兩部分組成:函數(shù)以及創(chuàng)建該函數(shù)的環(huán)境。函數(shù)就是被外部函數(shù)返回的內(nèi)部函數(shù),而環(huán)境就是由閉包創(chuàng)建時在作用域中的任何局部變量組成的。在我們的例子中,result 是一個閉包,由函數(shù) fun1 和閉包創(chuàng)建時存在的「"Google"」字符串形成。

現(xiàn)在想想,維基百科說的好像就是這么回事:閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實體。這就解釋了為什么可以讀取變量 name 了,因為 result 引用的環(huán)境是 fun1 函數(shù)相關(guān)的引用環(huán)境,可以理解為: result 處在 fun1 所處的作用域鏈的位置,既然這樣,那自然可以讀取變量 name 了。

這就是閉包,現(xiàn)在看來也就是這么回事么,沒什么難理解的。

既然已經(jīng)理解了,那我們再來看一個例子(引用自廖雪峰老師的 JS 教程):

這個例子中,每次循環(huán),都創(chuàng)建了一個新的函數(shù),然后,把創(chuàng)建的 3 個函數(shù)都添加到數(shù)組 arr 中返回了。

那么調(diào)用 f1() 、f2()、f3() 的結(jié)果是什么呢?不就是 1,4,9 嗎? 不是。

你沒看錯,答案就是 16,全部都是!原因在于閉包 results 返回的數(shù)組中的函數(shù)引用了變量 i,但這個返回的數(shù)組中的函數(shù)并不是立刻執(zhí)行的,等到執(zhí)行時,它們所引用的變量 i 已經(jīng)變成 4 了,所以結(jié)果為 16。還是沒明白?上面我們說了,閉包是由函數(shù)和其相關(guān)的引用環(huán)境組合而成的,既然所處的環(huán)境還是在作用域鏈原來的位置,那么變量 i 就會在 for 循環(huán)的作用下變成 4,而到了你去調(diào)用閉包的時候,閉包引用的變量 i 的值自然為 4 了,所以結(jié)果自然就是 16 了。

這個例子要提醒大家的是:返回的函數(shù),不要引用任何循環(huán)變量和變量值后續(xù)會發(fā)生變化的變量。這一點在使用閉包時一定要牢記。

還要說明的一點就是,避免濫用閉包。原因:使用閉包之后,閉包中函數(shù)所處的環(huán)境會一直存在,所以閉包會使得函數(shù)中的變量都被保存在內(nèi)存中,不會被“垃圾回收機制”回收,進而內(nèi)存消耗過大,造成網(wǎng)頁性能下降。

最后,理解清楚作用域鏈的概念不但對掌握閉包非常重要,并且對其他知識點(比如 with 語句)同樣很重要。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 閉包就是指一個有權(quán)訪問另外一個函數(shù)作用域中的變量的函數(shù)。--《JavaScript高級程序第三版》 本人對于閉包初...
    車大棒閱讀 522評論 0 0
  • 寫這篇文章的原因很簡單,臨近畢業(yè),不得不考慮找工作的問題,面臨著就業(yè)的壓力,開始在前輩的教育下,開始刷題之旅,從l...
    BeLLESS閱讀 1,072評論 0 2
  • 王福朋 - 博客園 —— 《 深入理解javascript原型和閉包》 目錄:深入理解javascript原型和閉...
    帥而不花__美而不浪閱讀 1,623評論 0 2
  • 閉包(closure)是Javascript語言的一個難點,也是它的特色,很多高級應(yīng)用都要依靠閉包實現(xiàn)。 一、變量...
    zock閱讀 1,118評論 2 6
  • 闌珊風雪入樓中 依曲水,流觴夢 渡河無捷岸 梅雨沐驚風 臨云不懼山高冷 一夜如姜翁 誰曾獨釣 江雪朦朧 問天外 來...
    婉約的風閱讀 420評論 0 0

友情鏈接更多精彩內(nèi)容