我們都知道 JS 都可以運(yùn)行在瀏覽器中,我們還知道它是一門(mén)弱類(lèi)型
,基于原型
的動(dòng)態(tài)腳本,那么它是不是只能在瀏覽器中運(yùn)行呢吭从?
答案是不是的,如今的JS已經(jīng)強(qiáng)大到不止瀏覽器這些平臺(tái)運(yùn)行了恶迈,還可以在Node環(huán)境涩金,WebView中運(yùn)行谱醇,這些都是基于我們強(qiáng)大的V8引擎所賜,賦予了 JS 脫離瀏覽器也可以運(yùn)行的能力步做。
那么 JS 又是如何在瀏覽器等其他平臺(tái)運(yùn)行的呢副渴?
這涉及到編譯原理,js在剛開(kāi)始就是一大坨字符串文本全度,瀏覽器中的解釋器(編譯器)會(huì)對(duì)這些字符串有序地進(jìn)行詞法解析
,語(yǔ)法解析
后生成AST
(抽象語(yǔ)法書(shū)),最后將AST
轉(zhuǎn)換成可執(zhí)行的代碼就是解釋器的最后一步工作代碼生成
煮剧。
從 JS 腳本加載到執(zhí)行的過(guò)程中會(huì)有3個(gè)巨佬圍著他,讓他順利運(yùn)行且執(zhí)行将鸵,咚咚咚勉盅,他們分別就是引擎
,解釋器
以及作用域
。
由于這些和我們的主題雖然有關(guān)聯(lián)顶掉,但是不是我們主題重點(diǎn)了解的知識(shí)點(diǎn)草娜,我們就點(diǎn)到為止,有機(jī)會(huì)繼續(xù)深究學(xué)習(xí)痒筒,望解宰闰,共勉,嘻嘻~
本文主要講解的是 JS 運(yùn)行過(guò)程中的堆棧內(nèi)存變化以及函數(shù)底層的處理機(jī)制
That's right! 這篇文章我們重點(diǎn)學(xué)習(xí)的知識(shí)點(diǎn)就是理解什么是ECStack
,EC
,VO
,AO
以及GO
通過(guò)上文我們了解到 JS 之所以可以在瀏覽器運(yùn)行是因?yàn)闉g覽器給它提供了供代碼運(yùn)行的環(huán)境凸克。
代碼在運(yùn)行前瀏覽器會(huì)分配一個(gè)內(nèi)存供代碼執(zhí)行议蟆,而這個(gè)內(nèi)存我們稱(chēng)之為ECStack
,我們也稱(chēng)之為執(zhí)行棧
。
ECStack (Execution Content Stack)
ECStack 是計(jì)算機(jī)分配的一塊內(nèi)存萎战,專(zhuān)門(mén)供代碼執(zhí)行
咐容。
EC (Execution Content)
代碼執(zhí)行
又可以分為全局代碼,函數(shù)中代碼蚂维,私有塊代碼等等戳粒,不同環(huán)境下的代碼執(zhí)行都會(huì)有自己的上下文,這些上下文就是EC
虫啥,我們也可以稱(chēng)之為執(zhí)行上下文
蔚约,也可以稱(chēng)之為當(dāng)前上下文環(huán)境
。
VO(Varibale Object)
VO
就是變量對(duì)象
涂籽,代碼在當(dāng)前上下文執(zhí)行時(shí)創(chuàng)建的變量總是會(huì)存儲(chǔ)在當(dāng)前上下文中指定的變量對(duì)象
中 苹祟,簡(jiǎn)單地說(shuō)就是變量對(duì)象就是用來(lái)儲(chǔ)存當(dāng)前上下文創(chuàng)建的創(chuàng)建的變量。
AO (Active Object)
其中评雌,很多童靴再學(xué)習(xí) VO
后在了解 AO
可能就會(huì)被弄暈树枫,不僅會(huì)浮現(xiàn)一個(gè)問(wèn)題 -- VO
與 AO
有什么瓜系?景东?砂轻?
當(dāng)初靚靚的筆者在初學(xué)的時(shí)候也有過(guò)這個(gè)問(wèn)題,迷惑了我好久斤吐,但是其實(shí)AO
可以理解為是 VO
的一種搔涝,區(qū)別就是 AO
是在函數(shù)中的 變量對(duì)象
厨喂,名曰 AO
,
又可以叫它為 二狗子 活動(dòng)對(duì)象
。
這就有趣了庄呈,為什么說(shuō)是同一個(gè)概念蜕煌,到函數(shù)中就變了呢?
其實(shí)我們?cè)趧?chuàng)建函數(shù)的過(guò)程中诬留,函數(shù)內(nèi)部的代碼不管對(duì)還是錯(cuò)我們都可以成功加載到頁(yè)面中幌绍,因?yàn)槲覀冞€沒(méi)有調(diào)用執(zhí)行它,也就是我們聲明一個(gè)函數(shù)時(shí)故响,你又沒(méi)有調(diào)用它傀广,那么它就失去了它的作用,相當(dāng)于內(nèi)部?jī)?chǔ)存的代碼塊那么和筆者一樣靚也沒(méi)有用彩届,就一坨字符串而已伪冰。
相反,如果我們創(chuàng)建它樟蠕,而且還有責(zé)任心地調(diào)用執(zhí)行它贮聂,實(shí)現(xiàn)了它的價(jià)值,高興地和用釘釘上課的孩子一樣寨辩,然后在內(nèi)部創(chuàng)建一個(gè) AO
吓懈,用來(lái)儲(chǔ)存體內(nèi)創(chuàng)建的變量,然后會(huì)綁定作用域鏈scope-chain
靡狞,<EC(FUNC),EC(G)> ,鏈的左端是當(dāng)前上下文EC(FUNC)
耻警,鏈的右側(cè)就是函數(shù)創(chuàng)建時(shí)所處的上下文,也叫函數(shù)的作用域 EC(G)
作用域鏈
PS: <EC(FUNC),EC(G)> 只是用來(lái)參考甸怕,以便理解8蚀!梢杭!
顧名思義温兼,作用域鏈就是一條連接多個(gè)不同的作用域的鏈,就和我們的原型鏈一樣武契,開(kāi)發(fā)的時(shí)候如果雙鏈齊用募判,哪怕別人學(xué)會(huì)了葵花寶典都打不過(guò)你了。
更為具體地來(lái)說(shuō)咒唆,作用域鏈就是當(dāng)前私有上下文代碼執(zhí)行届垫,遇到一個(gè)變量,首先會(huì)問(wèn)私有上下文中AO
是否創(chuàng)建過(guò)這么一個(gè)變量钧排,如果有的話(huà)就自給自足敦腔,接下來(lái)的操作都在于私有操作均澳,和其他上下文則沒(méi)有任何關(guān)系恨溜。如果沒(méi)有呢符衔?不是自己私有變量的話(huà)就會(huì)通過(guò)作用域鏈走向上級(jí)的上下文中查找,如果找到了就取它糟袁,沒(méi)有的話(huà)就重復(fù)繼續(xù)往在上級(jí)的上下文查找判族,直到訪(fǎng)達(dá)到全局上下文EC(G)
,如果找到了就取它项戴,如果取不到就看是什么操作形帮,假如是獲取值的話(huà)找不到也沒(méi)有用方法,只能給你來(lái)一句 “梁非凡,……” ** is not define
周叮;但是如果是設(shè)置的話(huà)辩撑,就會(huì)在全局對(duì)象 GO
(下文會(huì)有解釋?zhuān)?中創(chuàng)建一個(gè)變量,并且掛載到 window
,和window中對(duì)應(yīng)的屬性變量呈現(xiàn)一種映射的關(guān)系仿耽。
同時(shí)函數(shù)執(zhí)行的過(guò)程中合冀,會(huì)生成一個(gè)封閉,私有的上下文项贺,外界無(wú)法訪(fǎng)問(wèn)君躺,用于保護(hù)里面的變量不受外界干擾,我們把這種函數(shù)執(zhí)行的這種保護(hù)機(jī)制稱(chēng)之為閉包
开缎。
GO (Global Object)
文章剛開(kāi)始的時(shí)候我們提及到瀏覽器會(huì)分配一塊內(nèi)存用來(lái)執(zhí)行 JS 代碼棕叫,就是我們的 ECStack
,同時(shí)也虧開(kāi)辟一個(gè)堆內(nèi)存奕删,存儲(chǔ)一些內(nèi)置的方法以及屬性俺泣,例如我們的setTimeout
,setInterval
,isNaN
等內(nèi)置屬性和方法,然后瀏覽器會(huì)在全局變量對(duì)象中創(chuàng)建一個(gè)變量在指向全局對(duì)象完残,也就是我們的 GO
砌滞,這個(gè)變量就是window
,在 Node 環(huán)境中的與全局對(duì)象掛鉤的變量是 global
坏怪。
最后不得不強(qiáng)調(diào)一點(diǎn)贝润,全局對(duì)象 !== 全局變量對(duì)象,這兩個(gè)含義完全不一樣铝宵。
紙上談兵終覺(jué)淺打掘,絕知此事要躬行。我準(zhǔn)備了一些簡(jiǎn)單的題目來(lái)更加直觀地理解上述的名詞性知識(shí)點(diǎn)鹏秋,深入淺出供童靴食用更佳尊蚁。
?? ?? ??
var a = 1;
var b = a;
b = 3;
console.log(a) // 1
為什么變量 a 不會(huì)隨著 b 的變化而變化呢?
因?yàn)閯傞_(kāi)始a 與 b 指向共用同一個(gè)值侣夷,但是后面 b 更換了新的值横朋,與原來(lái)的 1 斷開(kāi)了聯(lián)系,和 2 開(kāi)始了新的旅程百拓,a 依然指向 1琴锭,并不會(huì)因?yàn)?b 的走心受到影響晰甚。
?? ?? ??
var a = {n: 1};
var b = a;
b.n = 3;
console.log(a.n) // 3
看圖我們可以看出,因?yàn)閯傞_(kāi)始a指向一個(gè)對(duì)象决帖,而對(duì)象和基本數(shù)據(jù)不太一樣厕九,基本數(shù)據(jù)是存儲(chǔ)在棧中,對(duì)象的話(huà)計(jì)算機(jī)會(huì)開(kāi)辟一個(gè)堆內(nèi)存來(lái)存儲(chǔ)這個(gè)對(duì)象地回,并且將這個(gè)堆的地址賦予對(duì)應(yīng)的變量扁远,但要對(duì)變量進(jìn)行操作的話(huà)通過(guò)地址對(duì)對(duì)應(yīng)堆中的對(duì)象進(jìn)行操作即可。
?? ?? ??
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
console.log(a.x) // undefined
?? ?? ??
var a = {n: 1};
function fun(o){
o.n = 2;
o = {x: 555};
o.y = 233;
console.log(o) // {x: 555,y: 233}
}
fun(a)
console.log(a) // {n: 2}
不多解釋?zhuān)蹅冞€是直接上圖吧刻像!
其中我們還有一點(diǎn)需要找到畅买,就是函數(shù)執(zhí)行的時(shí)候發(fā)生了什么。
在我們函數(shù)執(zhí)行的時(shí)候细睡,在未執(zhí)行之前皮获,函數(shù)體內(nèi)的代碼是被視為字符串存放到堆內(nèi)存中的,所以在執(zhí)行會(huì)解析這些代碼字符串纹冤,并且執(zhí)行它洒宝;
在執(zhí)行的過(guò)程中
- 形成一個(gè)全新的私有上下文
- 有存儲(chǔ)私有上下文中,聲明的私有變量空間
AO
- 將上下文進(jìn)棧執(zhí)行
- 有存儲(chǔ)私有上下文中,聲明的私有變量空間
- 在執(zhí)行前需要做的事情
- 初始化作用域鏈
- 初始化 this 指向
- 初始化 Arguments
- 形參賦值
- 變量提升
- 代碼執(zhí)行
- 一般情況下萌京,函數(shù)執(zhí)行完成后雁歌,為了優(yōu)化棧內(nèi)存,會(huì)將形成的私有上下文移出棧釋放掉知残,這就是我們一直說(shuō)的“GC瀏覽器垃圾回收機(jī)制”
以上就是筆者在這方面學(xué)習(xí)的總結(jié)靠瞎,你是否 Get 到了呢?
記得點(diǎn)擊關(guān)注和給波贊求妹,第一時(shí)間收到筆者專(zhuān)門(mén)為你推送的前端干貨乏盐,你還“下次一定”嗎,嚶嚶嚶~