摘要:換句話說,定義在閉包中的函數可以記憶它被創建時候的環境。詞法環境的概念定義摘自百科。一個詞法環境由一個環境記錄項和可能為空的外部詞法環境引用構成。中使用詞法環境管理靜態作用域。
一個資深的同事在我出發去面試前告誡我,問JS知識點的時候千萬別主動提閉包,它就是一個坑啊!坑啊!啊!
閉包確實是js的難點和重點,其實也沒那么可怕,關鍵是機制的理解,可以和函數一起多帶帶拿出來說說,其實關于閉包的解釋很多文章都寫得比較詳細了,這篇文章就作為自己學習過程的記錄吧。
閉包的概念首先明確一下閉包的概念:
MDN (Mozilla Develop Network) 上的對閉包的定義:
閉包是指能夠訪問自由變量的函數 (變量在本地使用,但在閉包中定義)。換句話說,定義在閉包中的函數可以“記憶”它被創建時候的環境。
分析:
閉包由函數和與其相關的引用環境(詞法環境)的組合而成
閉包允許函數訪問其引用環境(詞法環境)中的變量(又稱自由變量)
廣義上來說,所有JS的函數都可以稱為閉包,因為JS函數在創建時保存了當前的詞法環境
還是很拗口有木有,一臉懵逼的時候就應該從基礎的概念開始找,所以我們來談談詞法環境。
詞法環境的概念定義(摘自wiki百科)。
變量作用域詞法環境是一個用于定義特定變量和函數標識符在ECMAScript代碼的詞法嵌套結構上關聯關系的規范類型。一個詞法環境由一個環境記錄項和可能為空的外部詞法環境引用構成。
一般來說,在編程語言中都有變量作用域的概念,每個變量都有自己的生命周期和作用范圍。
作用域有兩種解析方式:
靜態作用域
又稱為詞法作用域,在編譯階就可以決定變量的引用,由程序定義的位置決定,和代碼執行順序無關,用嵌套的方式解析。
動態作用域
在程序運行時候,和代碼的執行順序決定。用動態棧動態管理。
var x = 10; function getX() { alert(x); } function foo() { var x = 20; getX(); } foo();
在靜態作用域下:
全局作用域下有x, getX, foo三個變量,getX和foo都有自己的作用域。執行foo函數的時候,getX()被執行,但是getX的定義位置在全局作用域下的,取到的x是10,而不是20。
在動態作用域下:
運行這段代碼時,先把x=10、getX、foo按順序壓棧,然后執行foo函數,在函數中把x=20壓棧,然后執行getX(),此時距離棧頂最近的x值為20,因此alert的值也是20。
JavaScript使用的變量作用域是靜態作用域。JS中作用域簡單分為兩部分:全局作用域和函數作用域。ES5中使用詞法環境管理靜態作用域。
詞法環境包含兩部分
環境記錄
形參
函數聲明
變量
其它...
對外部詞法環境的引用(outer)
環境記錄初始化一段JS代碼執行之前,會對環境記錄進行初始化(聲明提前),即將函數的形參、函數聲明和變量先放入函數的環境記錄中,特別需要注意的是:
形參會在初始化的時候定義值,但是函數內部定義的變量只聲明不定義(不賦值),這個需要用JS中的Hoisting機制來解釋,具體可以看這一篇文章:《理解 JavaScript(二):Scoping & Hoisting》。
以下面這段代碼為例,解析環境記錄初始化和代碼執行的過程:
var x = 10; function foo(y) { var z = 30; function bar(q) { return x + y + z + q; } return bar; } var bar = foo(20); bar(40);
step1:初始化全局環境
全局環境 | |
---|---|
環境記錄(record) | foo: |
x: undefined(聲明變量而非定義變量) | |
bar: undefined(聲明變量而非定義變量) | |
外部環境(outer) | null |
step2: 執行x=10
全局環境 | |
---|---|
環境記錄(record) | foo: |
x: 10() | |
bar: undefined(聲明變量而非定義變量) | |
外部環境(outer) | null |
step3:執行var bar = foo(20)語句之前,將foo函數的環境記錄初始化
foo 環境 | |
---|---|
環境記錄(record) | y: 20(定義形參) |
bar: |
|
z: undefined(聲明變量而非定義變量) | |
外部環境(outer) | 全局環境 |
step4:執行var bar = foo(20)語句,變量bar接收foo函數中返回的bar函數
foo 環境 | |
---|---|
環境記錄(record) | y: 20 |
bar: |
|
z: 30(定義z) | |
外部環境(outer) | 全局環境 |
step5:執行bar函數之前,初始化bar的詞法環境
bar環境 | |
---|---|
環境記錄(record) | q: 40(定義形參q) |
外部環境(outer) | foo環境 |
step6:在foo函數內執行bar函數
x + y + z + q = 10 + 20 + 30 + 40 = 100
其實說了那么多,也是想強調一點:形參的值在環境初始化的時候就賦值了!因此形參的作用之一就是保存外部變量的值!
一道閉包的面試題查了一下關于閉包的面試題,用具體的例子說明閉包的應用場景。
最常見的答案來自于《JavaScript高級程序設計(第3版)》p181:
例子:
function creacteFunctions() { var result = new Array(); for (var i = 0; i < 10; i++) { result[i] = function () { return i; } } return result; }
這個函數返回了長度為10的函數數組,假設我們調用函數數組的第3個函數,在控制臺中輸入creacteFunctions()[2](),即執行函數數組里面的第三個函數,creacteFunctions()返回函數數組,[2]是取第三個函數的引用,最后一個()是執行第三個函數,返回結果卻并不是預期的2,而是10.
因此,為了能夠讓閉包的行為符合預期,需要創建一個匿名函數:
function creacteFunctions() { var result = new Array(); for (var i = 0; i < 10; i++) { result[i] = function (num) { return function() { return num; } }(i); } return result; }
此時在控制臺中輸入creacteFunctions()[2](),即執行函數數組里面的第三個函數,返回的就是預期中的2。
有了詞法環境的初始化過程,這里也就非常容易理解了。匿名函數的形參num保存了每次執行的i的值。在function(num){...}(i)這個結構中,i作為形參num的實際值執行這個匿名函數,因此每次循環中的num直接初始化為i的值。
為了更清楚的提取這部分結構,我們將匿名函數命名為helper:
var helper = function (num) { return function() { return num; } }
用helper函數重寫第二段代碼:
var helper = function (num) { return function() { return num; } } function creacteFunctions() { var result = new Array(); for (var i = 0; i < 10; i++) { result[i] = helper(i); } return result; }
在控制臺中輸入creacteFunctions()[2](),輸出的也是預期中的2。
未完待續哦,閉包可以講的東西太多啦!
一句話總結真正理解了作用域也就理解了閉包.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/80193.html
摘要:一言以蔽之,閉包,你就得掌握。當函數記住并訪問所在的詞法作用域,閉包就產生了。所以閉包才會得以實現。從技術上講,這就是閉包。執行后,他的內部作用域并不會消失,函數依然保持有作用域的閉包。 網上總結閉包的文章已經爛大街了,不敢說筆者這篇文章多么多么xxx,只是個人理解總結。各位看官瞅瞅就好,大神還希望多多指正。此篇文章總結與《JavaScript忍者秘籍》 《你不知道的JavaScri...
摘要:所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。所以本文中將以維基百科中的定義為準即在計算機科學中,閉包,又稱詞法閉包或函數閉包,是引用了自由變量的函數。 閉包(closure)是JavaScript中一個神秘的概念,許多人都對它難以理解,我也一直處于似懂非懂的狀態,前幾天深入了解了一下執行環境以及作用域鏈,可戳查看詳情,而閉包與作用域及作用域鏈的關系密不可分,所...
摘要:建筑的頂層代表全局作用域。實際的塊級作用域遠不止如此塊級作用域函數作用域早期盛行的立即執行函數就是為了形成塊級作用域,不污染全局。這便是閉包的特點吧經典面試題下面的代碼輸出內容答案個如何處理能夠輸出閉包方式方式下一篇你不知道的筆記 下一篇:《你不知道的javascript》筆記_this 寫在前面 這一系列的筆記是在《javascript高級程序設計》讀書筆記系列的升華版本,旨在將零碎...
摘要:閉包的定義閉包是函數和聲明該函數的詞法作用域的組合。上面的和都是閉包。然而在一個閉包內對變量的修改,不會影響到另一個閉包中的變量。原因是賦值給的是閉包。由于循環在事件觸發之前早已執行完畢,變量被三個閉包共享已經變成了。 閉包的定義: 閉包是函數和聲明該函數的詞法作用域的組合。 先看如下例子: function makeFn(){ var name = Mirror; f...
閱讀 2418·2021-11-16 11:44
閱讀 1877·2021-10-12 10:12
閱讀 2160·2021-09-22 15:22
閱讀 3008·2021-08-11 11:17
閱讀 1505·2019-08-29 16:53
閱讀 2653·2019-08-29 14:09
閱讀 3474·2019-08-29 14:03
閱讀 3301·2019-08-29 11:09