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