深入學(xué)習(xí)JavaScript執(zhí)行上下文
前言
我們先不看這個(gè)標(biāo)題,來看下面這段代碼是怎么運(yùn)行的:
var num1=1 var num1=2 var result=num1+num2 console.log(result)
我相信,小伙伴都知道打印的是3把,這也太簡(jiǎn)單了,但是我們思考下,這段代碼執(zhí)行時(shí),到底進(jìn)行了怎樣的操作,這里我們要引進(jìn)一個(gè)概念:初始化全局對(duì)象和執(zhí)行上下文
初始化全局對(duì)象(GO)
JS引擎在解析代碼的時(shí)候,會(huì)在堆內(nèi)存中創(chuàng)建一個(gè)全局對(duì)象叫:Gobal Object(GO)
- 該對(duì)象所有的作用域都可以訪問
- 里面會(huì)包含Date Array String Number setTimeout setInterval 等等
- 還有一個(gè)windows屬性指向本身
執(zhí)行上下文
代碼運(yùn)行時(shí):js引擎(這里考慮V8引擎)會(huì)創(chuàng)建執(zhí)行上下文棧(Execution context stack)簡(jiǎn)稱ECS,它就是執(zhí)行代碼的調(diào)用棧
執(zhí)行上下文分為:
- 全局執(zhí)行上下文( GEC )
- 函數(shù)執(zhí)行上下文( FEC )
這里我們先分析下全局執(zhí)行上下文,因?yàn)槲覀冮_頭那段代碼是全局代碼
全局執(zhí)行上下文
在執(zhí)行全局代碼的時(shí)候,會(huì)創(chuàng)建全局執(zhí)行上下文( GEC ),GEC會(huì)被放入ECS中去執(zhí)行 GEC被放入ECS
包含兩部分:
- 第一部分:在代碼執(zhí)行前,也就是V8引擎將parser轉(zhuǎn)成AST的過程中 ( 這里會(huì)涉及到V8引擎在解析js代碼的過程,會(huì)開篇專題講這個(gè)),會(huì)將全局定義的變量放入GO中,但是并不會(huì)賦值,因?yàn)槭?strong>解析階段 (pass:這個(gè)過程也被叫做變量的作用域提升)
- 第二部分:執(zhí)行代碼,并為GO里面的變量賦值,或者執(zhí)行其他函數(shù) 流程圖如下:圖中執(zhí)行前是上面描述的第一部分,開始執(zhí)行代碼是第二部分
Java Script遇到函數(shù)代碼如何執(zhí)行?
當(dāng)js引擎遇到函數(shù)執(zhí)行時(shí),會(huì)創(chuàng)建一個(gè) 函數(shù)執(zhí)行上下文( Functional Execution Contex ) 簡(jiǎn)稱FEC,并壓入到執(zhí)行上下文棧ECS
FEC中包含三部分內(nèi)容:
- 第一部分:VO(variable object ) 對(duì)象,其實(shí)也是AO( Activation Object )
- 第二部分:作用域鏈(scope chain):由自己的VO+父級(jí)的VO,查找時(shí)會(huì)一層一層去查找
- 第三部分:this綁定( 這里不做詳細(xì)描述,后續(xù)會(huì)出專題 )
作用域是解析編譯的時(shí)候就決定了,并不是執(zhí)行調(diào)用的時(shí)候來我們看一段代碼,一起看一下函數(shù)的執(zhí)行過程,一起來思考下這段代碼的運(yùn)行結(jié)果
var message = "Global" function foo() { console.log(message) } function bar() { var message = "Bar" foo() } bar()
畫個(gè)圖分析一下吧
首先初始化全局對(duì)象GO
,里面有Array、date、setTime
等等,還有自己定義的 message對(duì)象初始值為undefined,foo和bar函數(shù)對(duì)象
開始執(zhí)行代碼前,會(huì)創(chuàng)建一個(gè)執(zhí)行上下文棧ESC
,開始執(zhí)行全局代碼,所以會(huì)創(chuàng)建一個(gè)全局執(zhí)行上下文棧GEC
,將GEC壓入棧底,全局執(zhí)行上下文包括兩部分:
第一部分代碼執(zhí)行前的VO對(duì)象
(這里的VO指向的是GO
)
第二部分是開始執(zhí)行代碼,執(zhí)行第一行時(shí) var message="Global"
,GO對(duì)象里的message就被改為Global
執(zhí)行完第一行后,開始執(zhí)行第9行代碼 bar(),這里是函數(shù)的調(diào)用執(zhí)行,js引擎會(huì)創(chuàng)建一個(gè)函數(shù)執(zhí)行上下文FEC,壓入棧中,FEC包含三部分:
第一部分:VO對(duì)象
,這里指向bar自己的AO對(duì)象(包括形參和函數(shù)中定義的變量
)
第二部分:作用域鏈scope chain
,自己的VO對(duì)象+parent VO對(duì)象
第三部分:this綁定
,這里是指向windows(這個(gè)后續(xù)會(huì)開專題講)
開始執(zhí)行bar函數(shù)里面的代碼,var message="Bar"
,會(huì)將bar的AO對(duì)象的message變量值從undefined改為"Bar"
,接下來在執(zhí)行foo()函數(shù),注意:此時(shí)bar函數(shù)還沒彈出棧,因?yàn)閒oo函數(shù)還在執(zhí)行
訪問一個(gè)變量的時(shí)候,會(huì)沿著作用域鏈一層一層往上找,最后沒有找到則會(huì)報(bào)錯(cuò)
執(zhí)行到bar函數(shù)的最后一行代碼是foo(),此時(shí)又是一個(gè)函數(shù)的調(diào)用執(zhí)行,又會(huì)創(chuàng)建一個(gè)foo的函數(shù)執(zhí)行上下文,也包含上述的三部分:VO對(duì)象(指向foo的AO對(duì)象) 作用域鏈 和this綁定
開始執(zhí)行foo函數(shù)代碼console.log(message)
,當(dāng)打印message的時(shí)候,會(huì)沿著作用域鏈一層層找
,foo的作用域鏈?zhǔn)亲约旱腁O+父級(jí)的VO(也就是GO對(duì)象)
,自己的AO對(duì)象為空,所以找到GO里面的message變量,最終打印"Global"
執(zhí)行完后,函數(shù)執(zhí)行上下文執(zhí)行完之后,就會(huì)彈出棧,foo的FEC先彈出棧,然后bar的FEC彈出棧,他們自個(gè)的AO對(duì)象最終也被釋放
環(huán)境變量和記錄
我們上述所說的VO是基于早期的ECMAS規(guī)范,官網(wǎng)是這樣說的:
這里借助coderwhy的翻譯:每個(gè)執(zhí)行上下文都關(guān)聯(lián)一個(gè)變量對(duì)象(Vriable Object
),在源代碼中變量和函數(shù)的聲明會(huì)被作為屬性加入到變量對(duì)象中,對(duì)于函數(shù)來說,參數(shù)也會(huì)被加入到VO中但是ECMA5以后的版本,官網(wǎng)做了一些詞匯用語(yǔ)的修改
每個(gè)執(zhí)行上下文都會(huì)關(guān)聯(lián)一個(gè)變量環(huán)境(variable Environment)
,在執(zhí)行代碼中變量和函數(shù)的聲明會(huì)被當(dāng)做環(huán)境記錄(Environment Record)
加入到變量環(huán)境中。
對(duì)于函數(shù)代碼,形參也會(huì)被當(dāng)做環(huán)境記錄加入到變量環(huán)境中
總結(jié):
- 在訪問一個(gè)變量時(shí),會(huì)沿著作用域鏈一層層往上找,最終沒找到,會(huì)報(bào)錯(cuò):未定義
- 作用域以及父級(jí)作用域是代碼在編譯階段就已經(jīng)確定了,和調(diào)用位置沒關(guān)系
匯總一些名詞解釋,我們來解釋一下:
名詞 | 解釋 |
---|---|
ECS | 執(zhí)行上下文棧 (Execution Context Stack),也可稱為調(diào)用棧 |
GEC | 全局執(zhí)行上下文(Global Execution Context),在執(zhí)行全局代碼前創(chuàng)建 |
FEC | 函數(shù)執(zhí)行上下文(Functional Execution Context),在執(zhí)行函數(shù)前創(chuàng)建 |
VO | Variable Object,早期ECMA規(guī)范中的變量環(huán)境,對(duì)應(yīng)Object |
VE | Variable Environment,最新ECMA規(guī)范中的變量環(huán)境,對(duì)應(yīng)環(huán)境記錄 |
GO | 全局對(duì)象(Global Object),解析全局代碼時(shí)創(chuàng)建,GEC中關(guān)聯(lián)的VO就是GO |
AO | 活動(dòng)對(duì)象(Activation Object),VO被激活就變成了AO,VO和AO本質(zhì)上是一個(gè)東西,它們其實(shí)都是同一個(gè)對(duì)象,只是處于執(zhí)行環(huán)境的不同生命周期 |
到此這篇關(guān)于深入學(xué)習(xí)JavaScript執(zhí)行上下文的文章就介紹到這了,更多相關(guān)JS執(zhí)行上下文內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- js bind 函數(shù) 使用閉包保存執(zhí)行上下文
- JS ES6中setTimeout函數(shù)的執(zhí)行上下文示例
- 深入理解JavaScript 中的執(zhí)行上下文和執(zhí)行棧
- 一篇文章弄懂javascript中的執(zhí)行棧與執(zhí)行上下文
- JavaScript ECMA-262-3 深入解析(一):執(zhí)行上下文實(shí)例分析
- 通過實(shí)例了解JS執(zhí)行上下文運(yùn)行原理
- Javascript執(zhí)行上下文順序的深入講解
- js 執(zhí)行上下文和作用域的相關(guān)總結(jié)
- 詳解JavaScript中的執(zhí)行上下文及調(diào)用堆棧
相關(guān)文章
Javascript實(shí)現(xiàn)的常用算法(如冒泡、快速、鴿巢、奇偶等)
這篇文章主要介紹了Javascript實(shí)現(xiàn)的常用算法,如冒泡、快速、鴿巢、選擇、木桶、奇偶等,需要的朋友可以參考下2014-04-04javascript十六進(jìn)制數(shù)字和ASCII字符之間的轉(zhuǎn)換方法
下面小編就為大家?guī)硪黄猨avascript十六進(jìn)制數(shù)字和ASCII字符之間的轉(zhuǎn)換方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-12-12使用Fuse.js實(shí)現(xiàn)高效的模糊搜索功能
在現(xiàn)代?Web?應(yīng)用程序中,實(shí)現(xiàn)高效的搜索功能是至關(guān)重要的,Fuse.js?是一個(gè)強(qiáng)大的?JavaScript?庫(kù),它提供了靈活的模糊搜索和文本匹配功能,使您能夠輕松實(shí)現(xiàn)出色的搜索體驗(yàn),文中代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-01-01javascript使用正則表達(dá)式實(shí)現(xiàn)注冊(cè)登入校驗(yàn)
這篇文章主要為大家詳細(xì)介紹了javascript使用正則表達(dá)式實(shí)現(xiàn)注冊(cè)登入校驗(yàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09JS 日期驗(yàn)證正則附asp日期格式化函數(shù)
JS 日期驗(yàn)證正則 asp日期格式化函數(shù),大家以后的日期就是能通過驗(yàn)證的了。2009-09-09