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