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