1.變量提升
JavaScript代碼在執(zhí)行的時(shí)候铃拇,利用var聲明的變量是會(huì)提升到代碼的開頭并且賦值為unddefined钞瀑。
使用函數(shù)聲明創(chuàng)建的函數(shù)也會(huì)被提升,稱為函數(shù)提升慷荔。
上述兩個(gè)機(jī)制的存在使得我們可以在變量和函數(shù)聲明之前使用它們雕什。
JavaScript代碼會(huì)經(jīng)過JavaScript引擎的編譯之后再執(zhí)行。代碼經(jīng)過編譯之后显晶,會(huì)生成包含變量對象和詞法環(huán)境的執(zhí)行上下文以及可執(zhí)行代碼贷岸。變量對象中保存的就是被提升的變量聲明和函數(shù)聲明。如果遇到var聲明的變量吧碾,變量對象中就會(huì)生成一個(gè)屬性名為變量名凰盔、屬性值為undefined的屬性;如果遇到函數(shù)聲明倦春,就會(huì)生成一個(gè)方法名為函數(shù)名,方法為函數(shù)本身的方法(函數(shù)存儲(chǔ)在堆內(nèi)存中落剪,方法名保留的是地址值睁本。)
簡單總結(jié)執(zhí)行機(jī)制。
- 編譯忠怖。進(jìn)行變量和函數(shù)聲明的提升呢堰,存儲(chǔ)在變量對象中。
- 執(zhí)行凡泣。順序執(zhí)行可執(zhí)行的代碼枉疼。
2.調(diào)用棧
JavaScript引擎在執(zhí)行代碼的時(shí)候,會(huì)創(chuàng)建執(zhí)行上下文鞋拟。主要有全局執(zhí)行上下文骂维,函數(shù)執(zhí)行上下文和eval函數(shù)的執(zhí)行上下文。首先在執(zhí)行JavaScript代碼前贺纲,就會(huì)創(chuàng)建一個(gè)全局執(zhí)行上下文壓入棧航闺,然后根據(jù)函數(shù)的調(diào)用順序依次將執(zhí)行上下文壓入棧。函數(shù)內(nèi)部代碼被執(zhí)行完畢猴誊,對應(yīng)的執(zhí)行上下文就會(huì)被彈出棧潦刃。
執(zhí)行上下文中包含變量對象(VO)和詞法環(huán)境兩個(gè)部分,執(zhí)行上下文是在JavaScript引擎編譯階段創(chuàng)建的懈叹,也就是代碼執(zhí)行前乖杠。
一段代碼
var a = 2
function add(b,c){
return b+c
}
function addAll(b,c){
var d = 10
result = add(b,c)
return a+result+d
}
addAll(3,6)
在開發(fā)者工具中給代碼打上斷點(diǎn)之后,在右邊的調(diào)用棧堆里面能看見目前調(diào)用棧中的執(zhí)行上下文有哪些澄成。
或者給代碼加上一個(gè)console.trace也可以胧洒。
var a = 2
function add(b,c){
console.trace()//查看函數(shù)調(diào)用關(guān)系
return b+c
}
function addAll(b,c){
var d = 10
result = add(b,c)
return a+result+d
}
addAll(3,6)
控制臺(tái)輸出結(jié)果:
棧溢出問題笆包,執(zhí)行上下文的調(diào)用棧是有大小的。如果創(chuàng)建一個(gè)遞歸函數(shù)并且不設(shè)置終止條件略荡,最終就會(huì)發(fā)生棧溢出庵佣。為避免棧溢出,需要盡量將遞歸任務(wù)分解成其他的小型任務(wù)來執(zhí)行汛兜。
3.塊級(jí)作用域
ES6之前JavaScript只有全局作用域和函數(shù)作用域巴粪,ES6通過let和const實(shí)現(xiàn)了塊級(jí)作用域。簡單來說一對大括號(hào)就是一個(gè)塊級(jí)作用域粥谬。ES6如何同時(shí)支持變量提升和塊級(jí)作用域肛根?
前面提到,函數(shù)調(diào)用時(shí)會(huì)創(chuàng)建執(zhí)行上下文漏策,執(zhí)行上下文中有兩個(gè)部分派哲,變量對象和詞法環(huán)境。支持塊級(jí)作用域?qū)嶋H上就是通過詞法環(huán)境實(shí)現(xiàn)的掺喻。比如下面這個(gè)代碼:
function foo(){
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a)
console.log(b)
}
console.log(b)
console.log(c)
console.log(d)
}
foo()
來分析一下這段代碼的如何執(zhí)行芭届,以及執(zhí)行上下文的情況。
- 1.調(diào)用函數(shù)感耙,創(chuàng)建執(zhí)行上下文褂乍。變量對象中有a,c兩個(gè)變量,值都是undefined(var聲明的變量的變量提升導(dǎo)致的)即硼。詞法環(huán)境中有一個(gè)變量b逃片,但是由于let會(huì)生成暫時(shí)性死區(qū),所以雖然b已經(jīng)存在了只酥,但是在聲明b之前的語句去訪問b瀏覽器都會(huì)報(bào)錯(cuò)褥实。
- 2.執(zhí)行大括號(hào)內(nèi)部代碼,此時(shí)a=1,b=2裂允。此時(shí)由于進(jìn)入了一個(gè)新的作用域塊损离,所以詞法環(huán)境中會(huì)將這個(gè)塊壓入棧,這個(gè)新的塊中也有兩個(gè)變量b,d叫胖,同樣在它們聲明之前不可訪問草冈。
- 3.console.log(a)。執(zhí)行這句代碼的時(shí)候瓮增,就涉及到了如何在詞法環(huán)境和變量對象中尋找變量了怎棱。首先會(huì)查找位于詞法環(huán)境的棧頂?shù)膲K中的變量,然后沿著這個(gè)順序一直向下查找到位于棧底的塊绷跑,如果都沒有找到則會(huì)進(jìn)入變量對象中去尋找變量拳恋。也就是說,優(yōu)先在詞法環(huán)境中尋找砸捏,其后再到變量對象中尋找谬运。
4.作用域和閉包
作用域隙赁。始終記住作用域是靜態(tài)的,并且作用域在函數(shù)被定義的時(shí)候就已經(jīng)是確定了的梆暖,不會(huì)再發(fā)生改變了伞访。
閉包。記住閉包產(chǎn)生的兩個(gè)條件轰驳,函數(shù)嵌套以及內(nèi)部的函數(shù)引用了外部函數(shù)的變量厚掷。有了閉包,即使外部函數(shù)已經(jīng)執(zhí)行完畢级解,它的執(zhí)行上下文也已經(jīng)被彈出了執(zhí)行棧冒黑,但是內(nèi)部的變量還是會(huì)被保留下來,當(dāng)內(nèi)部函數(shù)被調(diào)用勤哗,這些被保存的變量隨時(shí)可以被這個(gè)內(nèi)部函數(shù)調(diào)用抡爹。就像一個(gè)專屬于內(nèi)部函數(shù)的背包一樣。
閉包的存在使得變量查找的作用域鏈發(fā)生了一定變化芒划。首先會(huì)在內(nèi)部函數(shù)自身的執(zhí)行上下文中尋找冬竟,之后進(jìn)入閉包中尋找,最后進(jìn)入全局作用域?qū)ふ摇?/p>
5.this
- 函數(shù)直接以函數(shù)形式被調(diào)用的時(shí)候腊状,this指向全局對象window
- call apply bind可以修改函數(shù)的this指向诱咏,使其指向參數(shù)中的對象
- 作為對象的方法被調(diào)用的時(shí)候,指向調(diào)用該方法的對象
- 構(gòu)造函數(shù)形式被調(diào)用缴挖,this指向利用構(gòu)造函數(shù)創(chuàng)建的對象
- 箭頭函數(shù)的this是靜態(tài)的,指向其被創(chuàng)建時(shí)所在的對象
嵌套函數(shù)焚辅,內(nèi)部的函數(shù)不會(huì)繼承外部函數(shù)的this指向
6.小結(jié)
本節(jié)重點(diǎn):
- JavaScript代碼也是需要先編譯后執(zhí)行的映屋,編譯的工作由JavaScript引擎來完成。
- 代碼經(jīng)過編譯后會(huì)生成執(zhí)行上下文和可執(zhí)行代碼同蜻,執(zhí)行上下文中由變量對象和詞法環(huán)境棚点。也解釋了為什么JavaScript會(huì)存在變量提升,其實(shí)是JavaScript引擎編譯的鍋湾蔓。
- var聲明和函數(shù)聲明的變量和函數(shù)會(huì)被存儲(chǔ)在變量環(huán)境中瘫析,變量的初始化值是undefined,函數(shù)則是函數(shù)的定義默责。let,const聲明的變量被存儲(chǔ)在詞法環(huán)境中贬循,形成暫時(shí)性死區(qū),也就是在聲明變量前不能對他們進(jìn)行訪問桃序。
- 每進(jìn)入一個(gè)新的代碼塊杖虾。詞法環(huán)境會(huì)創(chuàng)建一個(gè)新的塊壓入棧,里面是這個(gè)代碼塊中使用let和const聲明的變量媒熊。
- JavaScript尋找變量的方式奇适。首先在詞法環(huán)境棧頂?shù)膲K中尋找坟比,然后依次向下到棧底的塊,最后再到變量對象中尋找嚷往。