詳解JavaScript中的作用域鏈與閉包
作用域鏈
首先來(lái)看看這段代碼:
var a = '喜羊羊'; function A(){ console.log(a); a = '美羊羊'; function B(){ console.log(a); } B(); } A();
在這里毫無(wú)疑問(wèn)結(jié)果肯定是我們想到的先打印喜羊羊,再打印美羊羊。因?yàn)樽饔糜蜴溌?,如果?dāng)前層沒(méi)找到,那么就去當(dāng)前層的上一級(jí)找。
那么再看這道
function bar() { console.log(myName) } function foo() { var myName = "極客邦" bar() } var myName = "極客時(shí)間" foo()
是不是感覺(jué)是打印極客邦?如果是的話,那么恭喜你,掉坑里了。(還不趕快爬起來(lái),補(bǔ)一補(bǔ)作用域鏈的知識(shí))。
為什么打印不是極客邦而是極客時(shí)間呢?
既然問(wèn)題出現(xiàn)在了對(duì)作用域鏈的理解上,那么就再回到作用域鏈的定義上吧。
其實(shí)在每個(gè)執(zhí)行上下文的變量環(huán)境中,都包含了一個(gè)外部引用,用來(lái)指向外部的執(zhí)行上下文,我們把這個(gè)外部引用稱為 outer。
比如上面那段代碼在查找 myName
變量時(shí),如果在當(dāng)前的變量環(huán)境中沒(méi)有查找到,那么 JavaScript 引擎會(huì)繼續(xù)在 outer 所指向的執(zhí)行上下文中查找
為了直觀理解,你可以看下面這張圖:
看到這張圖我猜你又納悶了,為什么bar
函數(shù)創(chuàng)建的執(zhí)行上下文中的outer會(huì)指向全局??
哈哈哈,這里就要涉及到了詞法作用域了
詞法作用域
詞法作用域就是指作用域是由代碼中函數(shù)聲明的位置來(lái)決定的,所以詞法作用域是靜態(tài)的作用域,通過(guò)它就能夠預(yù)測(cè)代碼在執(zhí)行過(guò)程中如何查找標(biāo)識(shí)符。
這么講可能不太好理解,你可以看下面這張圖:
從圖中可以看出,詞法作用域就是根據(jù)代碼的位置來(lái)決定的,其中 main
函數(shù)包含了 bar
函數(shù),bar
函數(shù)中包含了 foo
函數(shù),因?yàn)?JavaScript 作用域鏈?zhǔn)怯稍~法作用域決定的,所以整個(gè)詞法作用域鏈的順序是:foo 函數(shù)作用域—>bar 函數(shù)作用域—>main 函數(shù)作用域—> 全局作用域。
明白了詞法作用域,那么我們?cè)倩氐絼倓偟膯?wèn)題。
為什么bar函數(shù)創(chuàng)建的執(zhí)行上下文中的outer會(huì)指向全局
這是因?yàn)楦鶕?jù)詞法作用域,而詞法作用域又是根據(jù)代碼的位置,而bar函數(shù)代碼的位置就是包裹在全局下,而喜羊羊那個(gè)例子中的B函數(shù)是在A函數(shù)的環(huán)境下,所以會(huì)造成它們的詞法作用域鏈不同,也就導(dǎo)致函數(shù)作用域鏈不同了。
所以我們才有那句話詞法作用域是代碼編譯階段就決定好的,和函數(shù)是怎么調(diào)用的沒(méi)有關(guān)系。
也就是只和代碼位置有關(guān),和函數(shù)直接如何調(diào)用沒(méi)關(guān)系
閉包
老生常談的問(wèn)題,這次再?gòu)囊粋€(gè)更深入的角度來(lái)理解一下。
看下面這段代碼:
function foo() { var myName = "極客時(shí)間" let test1 = 1 const test2 = 2 var innerBar = { getName:function(){ console.log(test1) return myName }, setName:function(newName){ myName = newName } } return innerBar } var bar = foo() bar.setName("極客邦") bar.getName() console.log(bar.getName())
這段代碼乍一看沒(méi)有什么問(wèn)題,但是這里有一個(gè)細(xì)節(jié)很多人會(huì)忽視。
在foo()執(zhí)行完將返回值給bar
時(shí),這里foo函數(shù)會(huì)從調(diào)用棧中彈出,變量都會(huì)被回收。既然變量都被回收了,那么bar.setName()
這些調(diào)用方法從何而來(lái)??
foo執(zhí)行完后的情況可以參考下圖:
從上圖可以看出,foo
函數(shù)執(zhí)行完成之后,其執(zhí)行上下文從棧頂彈出了,但是由于返回的 setName
和 getName
方法中使用了 foo 函數(shù)內(nèi)部的變量 myName
和 test1
,所以這兩個(gè)變量依然保存在內(nèi)存中。這像極了 setName
和 getName
方法背的一個(gè)專屬背包,無(wú)論在哪里調(diào)用了 setName
和 getName
方法,它們都會(huì)背著這個(gè)foo
函數(shù)的專屬背包。
之所以是專屬背包,是因?yàn)槌?setName
和 getName
函數(shù)之外,其他任何地方都是無(wú)法訪問(wèn)該背包的,我們就可以把這個(gè)背包稱為 foo 函數(shù)的閉包。
好了,現(xiàn)在我們終于可以給閉包一個(gè)正式的定義了。在 JavaScript 中,根據(jù)詞法作用域的規(guī)則,內(nèi)部函數(shù)總是可以訪問(wèn)其外部函數(shù)中聲明的變量,當(dāng)通過(guò)調(diào)用一個(gè)外部函數(shù)返回一個(gè)內(nèi)部函數(shù)后,即使該外部函數(shù)已經(jīng)執(zhí)行結(jié)束了,但是內(nèi)部函數(shù)引用外部函數(shù)的變量依然保存在內(nèi)存中,我們就把這些變量的集合稱為閉包 比如外部函數(shù)是 foo,那么這些變量的集合就稱為 foo 函數(shù)的閉包。
用一句話概括就是
能夠訪問(wèn)其他函數(shù)內(nèi)部變量的函數(shù),被稱為 閉包。
(我們理解可以這么理解,但是和面試官說(shuō)的當(dāng)然可以把這個(gè)例子說(shuō)一下,這直接上升到了一個(gè)理解什么是閉包的新高度了)
到此這篇關(guān)于詳解JavaScript中的作用域鏈與閉包的文章就介紹到這了,更多相關(guān)JavaScript作用域鏈 閉包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript ES5標(biāo)準(zhǔn)中新增的Array方法
這篇文章主要介紹了JavaScript ES5標(biāo)準(zhǔn)中新增的Array方法 的相關(guān)資料,需要的朋友可以參考下2016-06-06JavaScript創(chuàng)建對(duì)象的方式小結(jié)(4種方式)
這篇文章主要介紹了JavaScript創(chuàng)建對(duì)象的方式,結(jié)合實(shí)例形式總結(jié)分析了四種創(chuàng)建對(duì)象的方式,并附帶分析了JavaScript對(duì)象復(fù)制的技巧,需要的朋友可以參考下2015-12-12基于JavaScript實(shí)現(xiàn)控制下拉列表
這篇文章主要介紹了基于JavaScript實(shí)現(xiàn)控制下拉列表,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05詳解js正則表達(dá)式驗(yàn)證時(shí)間格式xxxx-xx-xx形式
本篇文章主要介紹了詳解js正則表達(dá)式驗(yàn)證時(shí)間格式xxxx-xx-xx形式,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02原生javascript實(shí)現(xiàn)DIV拖拽并計(jì)算重復(fù)面積
這篇文章主要介紹了使用原生javascript實(shí)現(xiàn)DIV拖拽并計(jì)算重復(fù)面積的方法及示例代碼分享,效果十分漂亮,需要的朋友可以參考下2015-01-01編寫(xiě)更好的JavaScript條件式和匹配條件的技巧(小結(jié))
這篇文章主要介紹了編寫(xiě)更好的JavaScript條件式和匹配條件的技巧(小結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06