摘要:示例代碼執(zhí)行上下文創(chuàng)建階段在這個(gè)階段上下文對(duì)象會(huì)生成,并創(chuàng)建變量對(duì)象創(chuàng)建作用域鏈確定的指向。全局對(duì)象是作用域鏈的頭,還意味著在頂層代碼中聲明的所有變量都將成為全局對(duì)象的屬性。
變量對(duì)象
這一節(jié)聊一下變量對(duì)象。都是干貨(^▽?zhuān)?
變量對(duì)象是函數(shù)運(yùn)行時(shí)數(shù)據(jù)的集合,存儲(chǔ)了在上下文中定義的變量和函數(shù),不同的函數(shù)的變量對(duì)象稍有不同。
還是從上下文說(shuō)起,javascript 引擎執(zhí)行到函數(shù)的時(shí)候會(huì)向上下文棧中壓入一個(gè)上下文。
上下文中包含:
name | - | |
---|---|---|
變量對(duì)象(VO, variable object) | 當(dāng)前函數(shù)定義的變量、函數(shù)、參數(shù) | |
作用域鏈(Scope chain) | 源代碼定義時(shí)形成的作用域鏈 | |
this |
偽代碼:
// 全局上下文的偽代碼 windowEC = { VO: Window, scopeChain: {}, this: Window }
作用域鏈為當(dāng)前函數(shù)提供上層可訪(fǎng)問(wèn)變量和函數(shù)的有序集合;
this為函數(shù)提供運(yùn)行時(shí)對(duì)象環(huán)境;
變量對(duì)象提供當(dāng)前函數(shù)定義時(shí)的變量和函數(shù);
上下文生成時(shí)包含的作用域鏈的形成,和this的指向原則在前面的章節(jié)已經(jīng)梳理過(guò)。
javascript 中主要有全局上下文、函數(shù)上下文。先了解一下函數(shù)調(diào)用時(shí)上下文棧的變化。
當(dāng)函數(shù)被調(diào)用的時(shí)候,一個(gè)新的上下文會(huì)被加入到上下文棧的頂部, 而后會(huì)運(yùn)行函數(shù)內(nèi)部的代碼塊。
所以一個(gè)執(zhí)行上下文或者說(shuō)函數(shù)的生命周期分為上下文創(chuàng)建階段、函數(shù)運(yùn)行階段,不同階段上下文棧狀態(tài)和變量對(duì)象屬性會(huì)不一樣。
示例代碼:
function foo (a) { var b = 1 function c () { console.log(a + b) // 100 } c() } foo(99)執(zhí)行上下文創(chuàng)建階段
在這個(gè)階段上下文對(duì)象會(huì)生成,并創(chuàng)建變量對(duì)象、創(chuàng)建作用域鏈、確定 this 的指向。
說(shuō)一千道一萬(wàn)還不如來(lái)張圖干脆。
foo 函數(shù)上下文創(chuàng)建完成后的棧狀態(tài)示意圖:
注意:此時(shí)函數(shù)內(nèi)表達(dá)式和語(yǔ)句未執(zhí)行,變量對(duì)象屬性值是根據(jù)規(guī)則被設(shè)置為初始值的。
運(yùn)行階段上下文生成完成后,進(jìn)入函數(shù)運(yùn)行階段。依次按照代碼順序執(zhí)行函數(shù)內(nèi)代碼(變量的賦值、表達(dá)式計(jì)算、語(yǔ)句的執(zhí)行,其他函數(shù)調(diào)用等)。
在該階段會(huì)訪(fǎng)問(wèn)或設(shè)置變量對(duì)象(活動(dòng)對(duì)象)屬性的值。當(dāng)執(zhí)行完 foo 函數(shù)內(nèi)第一行代碼var b = 1,此時(shí)棧狀態(tài):
以此類(lèi)推,每次函數(shù)表達(dá)式執(zhí)行時(shí)都會(huì)在執(zhí)行上下文長(zhǎng)中獲取標(biāo)識(shí)符的值,通過(guò)運(yùn)算后又將結(jié)果保存在指定的標(biāo)識(shí)符里。因此執(zhí)行上下文為函數(shù)提供一個(gè)類(lèi)似于寄存器的概念來(lái)管理數(shù)據(jù)的功能。
當(dāng)函數(shù)執(zhí)行完后,對(duì)應(yīng)的執(zhí)行上下文被銷(xiāo)毀。JavaScript 執(zhí)行器會(huì)返回父函數(shù)或依據(jù)源代碼順序跳轉(zhuǎn)到其他函數(shù)。
函數(shù)上下文在函數(shù)上下文中,我們用活動(dòng)對(duì)象(activation object, AO)來(lái)表示變量對(duì)象。
活動(dòng)對(duì)象和變量對(duì)象其實(shí)是一個(gè)東西,只是變量對(duì)象是規(guī)范上的或者說(shuō)是引擎實(shí)現(xiàn)上的,不可在 JavaScript 環(huán)境中訪(fǎng)問(wèn),只有到當(dāng)進(jìn)入一個(gè)執(zhí)行上下文中,這個(gè)執(zhí)行上下文的變量對(duì)象才會(huì)被激活,所以才叫 activation object ,而只有被激活的變量對(duì)象,也就是活動(dòng)對(duì)象上的各種屬性才能被訪(fǎng)問(wèn)。
活動(dòng)對(duì)象也是在進(jìn)入函數(shù)上下文時(shí)刻被創(chuàng)建的,活動(dòng)對(duì)象是變量對(duì)象的一種激活狀態(tài)。所以當(dāng)你把變量對(duì)象和活動(dòng)對(duì)象記混淆了不要緊,因?yàn)樗麄儽举|(zhì)上對(duì)我們理解函數(shù)調(diào)用時(shí)的細(xì)節(jié)沒(méi)有影響。我在大多數(shù)時(shí)候也直接用變量對(duì)象來(lái)表述。
變量對(duì)象的創(chuàng)建變量對(duì)象的創(chuàng)建主要是進(jìn)行標(biāo)識(shí)符值類(lèi)型的申明和初始化,遵從下面這3條原則:
生成 arguments 對(duì)象。檢查當(dāng)前上下文的形參,生成屬性與屬性值對(duì)象(key: value)
當(dāng)形參沒(méi)有被賦值時(shí), 屬性值被設(shè)置為 undefined
在變量對(duì)象上建立函數(shù)索引。檢查當(dāng)前作用域定義的 function,在變量對(duì)象中以函數(shù)名為 key, 以函數(shù)所在內(nèi)存地址為 value 建立索引。
如果函數(shù)名已經(jīng)在變量對(duì)象中,則該函數(shù)名對(duì)應(yīng)的函數(shù)會(huì)被新的函數(shù)替換。所以函數(shù)可以被重復(fù)定義,后定義的函數(shù)會(huì)覆蓋掉先前定義的。
申明變量。檢查當(dāng)前作用域定義的變量,在變量對(duì)象中以變量名為 key, 以 undefined 為值掛載內(nèi)部變量。
如果新申明的變量名與已經(jīng)申明的形參名、函數(shù)名相同,則申明會(huì)被拋棄。
所以需要注意的是函數(shù)申明比變量申明優(yōu)先級(jí)高,一旦函數(shù)申明占用了某一個(gè)標(biāo)識(shí)符,后續(xù)的變量申明如果使用的是先前使用過(guò)的函數(shù)標(biāo)識(shí)符, 則該變量申明無(wú)效。
栗子1:
function foo () { function too() { } var too console.log(typeof too) } foo() // function // 變量 too 的申明無(wú)效
我們把上面的栗子稍作修改, 栗子2:
function foo () { function too() { } var too = 1 console.log(typeof too) } foo() // number // 變量 too 的值類(lèi)型為 number
變量 too 的值類(lèi)型為 number, 看到這個(gè)栗子大家可能會(huì)疑惑, 因?yàn)楦鶕?jù)上面的3條規(guī)則, too 的第二次申明應(yīng)該是無(wú)效的且 too 的類(lèi)型應(yīng)該為 function。 其實(shí) too 的值類(lèi)型在上下文創(chuàng)建階段確實(shí)是 function, 由于 javascript 是動(dòng)態(tài)弱類(lèi)型語(yǔ)言, 在上下文執(zhí)行階段 var too = 1 實(shí)質(zhì)是在給 too 賦值并且發(fā)生了隱式類(lèi)型轉(zhuǎn)換, 所以在執(zhí)行階段 too 變成了 number 類(lèi)型。es6 語(yǔ)法中已經(jīng)不建議使用var 來(lái)申明變量了, 而是使用let 來(lái)申明局部變量,從語(yǔ)法層面強(qiáng)制避免了重復(fù)的變量申明, 這樣栗子2中的情況會(huì)直接報(bào)錯(cuò)。
將上面的栗子再次修改,進(jìn)一步探索:
function foo () { function too() { } console.log(typeof too) // function var too = 1 console.log(typeof too) // number } foo()
foo 函數(shù)運(yùn)行時(shí)會(huì)先打印 ‘function’,然后打印 ‘number’。首先表達(dá)式console.log(typeof too)執(zhí)行時(shí)標(biāo)識(shí)符too在上下文創(chuàng)建階段被初始化為一個(gè)函數(shù)。var too = 1執(zhí)行后標(biāo)識(shí)符too被賦值為 1,所以第二次console.log(typeof too)的時(shí)候輸出的是number.
再再舉一個(gè)例子:
function foo () { console.log(a) console.log(bar) var a = 1 function bar() { return 2 } } foo() // undefind // ? bar() { // return 2 // }
上下文創(chuàng)建階段解析函數(shù)內(nèi)代碼塊后,會(huì)在變量對(duì)象上添加 ‘a(chǎn)’, ‘bar’ 兩個(gè)標(biāo)識(shí)符,并填充相應(yīng)的值結(jié)束上下文的創(chuàng)建階段進(jìn)入foo 函數(shù)的執(zhí)行階段。
在執(zhí)行階段 foo 函數(shù)體第一行表達(dá)式要求打印輸出 a 的值, 由于 console.log(a) 之前沒(méi)有對(duì) a 進(jìn)行任何賦值操作,根據(jù)規(guī)則此時(shí) a 的值為 undefind 所以輸出 "undefind"。函數(shù)體內(nèi)第二行要求打印輸出 bar 的值,根據(jù)規(guī)則標(biāo)識(shí)符 "bar" 對(duì)應(yīng)的是函數(shù),所以 "bar" 的值為函數(shù)實(shí)體,且在 console.log(bar) 之前也未對(duì) bar 做賦值操作,所以打印出來(lái)的是該函數(shù)。
變量對(duì)象創(chuàng)建完,函數(shù)運(yùn)行前的變量對(duì)象是這樣的:
// VO 為 Variable Object的縮寫(xiě),即變量對(duì)象 VO = { arguments: {...}, bar:// 表示bar的地址引用 a: undefined }
變量對(duì)象(活動(dòng)對(duì)象)的創(chuàng)建過(guò)程實(shí)質(zhì)上就是我們經(jīng)常提起的函數(shù)變量提升,這里3條原則才是變量提升的本質(zhì)。
如果沒(méi)有理解透徹可以回頭看看前面的內(nèi)容。
全局上下文在瀏覽器中,全局對(duì)象就是 window ,也是瀏覽器提供的預(yù)定義變量對(duì)象,可以通過(guò) this和self 引用。全局對(duì)象提供瀏覽器預(yù)置對(duì)象 Array、Object、console.log、alert 等,全局上下文的生命周期和函數(shù)的生命周期一樣,只要程序運(yùn)行不結(jié)束全局上下文就一直存在。其他所有的上下文環(huán)境,都能直接訪(fǎng)問(wèn)全局上下文的屬性。
全局對(duì)象是預(yù)定義的對(duì)象,作為 JavaScript 的全局函數(shù)和全局屬性的占位符。通過(guò)使用全局對(duì)象,可以訪(fǎng)問(wèn)所有其他所有預(yù)定義的對(duì)象、函數(shù)和屬性。在頂層 JavaScript 代碼中,可以用關(guān)鍵字 this 引用全局對(duì)象。因?yàn)槿謱?duì)象是作用域鏈的頭(最外層作用域),這意味著所有非限定性的變量和函數(shù)名都會(huì)作為該對(duì)象的屬性來(lái)查詢(xún)。例如,當(dāng)JavaScript 代碼引用 parseInt() 函數(shù)時(shí),它引用的是全局對(duì)象的 parseInt 屬性。全局對(duì)象是作用域鏈的頭,還意味著在頂層 JavaScript 代碼中聲明的所有變量都將成為全局對(duì)象的屬性。
通過(guò)this、seif訪(fǎng)問(wèn)全局對(duì)象:
console.log(this) console.log(self)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/107802.html
摘要:講清楚之參數(shù)傳值參數(shù)傳值是指函數(shù)調(diào)用時(shí),給函數(shù)傳遞配置或運(yùn)行參數(shù)的行為,包括通過(guò)進(jìn)行傳值。所以對(duì)的賦值會(huì)改變上下文棧中標(biāo)識(shí)符保存的具體值此時(shí)如果使用的是按引用傳遞,則變量所指向的對(duì)象因該也被賦值為。 講清楚之 javascript 參數(shù)傳值 參數(shù)傳值是指函數(shù)調(diào)用時(shí),給函數(shù)傳遞配置或運(yùn)行參數(shù)的行為,包括通過(guò)call、apply 進(jìn)行傳值。 在實(shí)際開(kāi)發(fā)中,我們總結(jié)javascript參數(shù)傳...
摘要:棧底為全局上下文,棧頂為當(dāng)前正在執(zhí)行的上下文。位于棧頂?shù)纳舷挛膱?zhí)行完畢后會(huì)自動(dòng)出棧,依次向下直至所有上下文運(yùn)行完畢,最后瀏覽器關(guān)閉時(shí)全局上下文被銷(xiāo)毀。 講清楚之執(zhí)行上下文 標(biāo)簽 : javascript 什么是執(zhí)行上下文? 當(dāng) JavaScript 代碼執(zhí)行一段可執(zhí)行代碼時(shí),會(huì)創(chuàng)建對(duì)應(yīng)的上下文(execution context)并將該上下文壓入上下文棧(context stack...
摘要:構(gòu)造函數(shù)和實(shí)例都通過(guò)屬性指向了原形。代碼示例是構(gòu)造函數(shù)的實(shí)例的屬性與的屬性保存的值相等,即他們指向同一個(gè)對(duì)象原形。 講清楚之javascript原型 標(biāo)簽: javascript javascript 中原形是一個(gè)比較難于理解的概念。javascript 權(quán)威指南在原形這一章也花了大量的篇幅進(jìn)行介紹,也許你已經(jīng)讀過(guò)javascript 權(quán)威指南,或者已經(jīng)是讀第N篇了,然而這篇文章的目...
閱讀 1325·2023-04-26 03:05
閱讀 782·2021-10-19 11:43
閱讀 3232·2021-09-26 09:55
閱讀 837·2019-08-30 15:56
閱讀 998·2019-08-30 15:44
閱讀 1250·2019-08-30 15:44
閱讀 2731·2019-08-30 14:23
閱讀 3247·2019-08-30 13:13