JavaScript編程的時候總規避不了聲明變量和函數,可是解釋器是如何而且在什麼地方去查找這些變量和函數呢?接下來,再延續上一篇《JavaScript系列之執行上下文和執行棧》,經過對變量對象(Variable Object)的介紹對執行上下文有一個更深一步的瞭解。javascript
上一篇文章也提到了,一個執行上下文的生命週期能夠分爲三個階段:java
詳細瞭解執行上下文對於初學者來講極爲重要,由於其中涉及到了變量對象,做用域鏈,this等不少JavaScript初學者沒徹底搞懂,且極爲重要的概念,它關係到咱們能不能真正理解JavaScript,真正理解也能更爲輕鬆地勝任後續工做,在後面的文章中咱們會一一詳細介紹,這裏咱們先重點了解一下變量對象。git
變量對象(Variable Object)是一個與執行上下文相關的數據做用域,存儲了在上下文中定義的變量和函數聲明,先來看一段代碼示例:github
function foo(){
var a = 10;
function b(){}
(function c(){});
console.log(a); // 10
console.log(b); // function b(){}
console.log(c); // Uncaught ReferenceError: c is not defined
}
foo();
複製代碼
在上面的例子中,foo()
函數的變量對象包含變量a
和函數b()
的聲明。這裏要注意的一點是,函數表達式並不像函數聲明同樣包含在變量對象中,在示例中所看到的那樣,訪問c()函數會致使引用錯誤。由於變量對象是抽象的和特殊的,它不能在代碼中訪問,但會由JavaScript引擎處理。express
上面利用的是函數上下文下的變量對象來講明變量對象儲存了什麼,但變量對象還存在於全局上下文中,接下來就分別來聊聊全局上下文中和函數上下文中的變量對象吧。編程
以瀏覽器中爲例,全局對象爲window
。 全局上下文有一個特殊的地方,它的變量對象,就是window
全局對象。而這個特殊,在this
指向上也一樣適用,this
也是指向window
。數組
// 以瀏覽器中爲例,全局對象爲window
// 全局上下文建立階段
// VO 爲變量對象(Variable Object)的縮寫
windowEC = {
VO: Window,
scopeChain: {},
this: Window
}
複製代碼
除此以外,全局上下文的生命週期,與程序的生命週期一致,只要程序運行不結束,好比關掉瀏覽器窗口,全局上下文就會一直存在。其餘全部的上下文環境,都能直接訪問全局上下文的屬性。瀏覽器
在上面已經提到了,變量對象存儲了執行上下文中的變量和函數聲明,但在函數上下文中,還多了一個arguments(函數參數列表)
, 一個僞數組對象。函數
這時變量對象的建立階段會包括:ui
arguments
對象。檢查當前上下文中的參數,創建該對象下的屬性與屬性值。var
聲明的變量),默認爲 undefined
;若是變量名稱跟已經聲明的形式參數或函數相同,爲了防止同名的函數被修改成undefined
,則會直接跳過變量聲明,原屬性值不會被修改。對於第3點中的「跳過」一詞想必你們會有一絲疑問?底下例子中既然按照上面的規則,變量聲明的foo
遇到函數聲明的foo
會跳過,但是爲何最後foo
的輸出結果仍然是被覆蓋了?
function foo() { console.log('I am function foo') }
var foo = 10;
console.log(foo); // 10
複製代碼
理由其實很簡單,由於上面的三條規則僅僅適用於變量對象的建立過程,也就是執行上下文的建立過程。而foo = 10
是在執行上下文的執行過程中運行的,輸出結果天然會是10。對比下例:
console.log(foo); // ƒ foo() { console.log('I am function foo') }
function foo() { console.log('I am function foo') }
var foo = 10;
console.log(foo); // 10
複製代碼
爲啥又是不同的結果呢?其實它的執行順序爲:
// 首先將全部函數聲明放入變量對象中,函數聲明變量提高
function foo() { console.log('I am function foo') }
// 其次將全部變量聲明放入變量對象中,可是由於foo已經存在同名函數,所以此時會跳過變量聲明默認undefined的賦值
// var foo = undefined;
// 而後開始執行階段代碼的執行
console.log(foo); // ƒ foo() { console.log('I am function foo') }
// 在執行上下文的執行過程當中運行
foo = 10;
console.log(foo); // 10
複製代碼
根據上面的規則,理解變量提高就變得十分簡單了,咱們也能夠看出,function
聲明會比var
聲明優先級更高一點。爲了幫助你們更好的理解變量對象,咱們再結合一個簡單的例子來進行探討。
function test() {
console.log(a);
console.log(foo());
var a = 1;
function foo() {
return 2;
}
}
test();
/* 結果爲: undefined 2 */
複製代碼
根據上述的規則,理解變量提高後能夠將執行順序理解爲:
function test() {
function foo() {
return 2;
}
var a;
console.log(a);
console.log(foo());
a = 1;
}
test();
複製代碼
這樣是否是一目瞭然了呢?
固然還須要注意的是,函數未進入執行階段以前,變量對象中的屬性都不能訪問!可是進入執行階段以後,變量對象(VO)轉變爲了活動對象(AO),而後開始進行執行階段的操做。
當前進入執行階段,變量對象(VO)激活成活動對象(AO),裏面的屬性都能被訪問了,函數會順序執行代碼,改變變量對象的屬性值,此階段的執行上下文代碼會分紅兩個階段進行處理:
當進入執行上下文時,這時候尚未執行代碼。讓咱們看一個例子:
function foo(a, b) {
var c = 10;
function d() {}
var e = function _e() {};
(function x() {});
}
foo(10);
複製代碼
當進入帶有參數10的foo
函數上下文時,AO表現爲以下:
AO = {
arguments: {
0: 10,
1: undefined,
length: 1
}
a: 10,
b: undefined,
c: undefined,
d: <function reference to d>, e: undefined, } 複製代碼
x
是函數表達式,因此不在變量對象當中,e
變量引用的值也是函數表達式,因此變量 e
自己是聲明,因此在變量對象當中。
這個階段會按順序執行代碼,修改變量對象的屬性值,緊接上面的例子,執行完成後AO以下:
AO = {
arguments: {
0: 10,
1: undefined,
length: 1
}
a: 10,
b: undefined,
c: 10,
d: <reference to function declaration d>,
e: <reference to Function expression to _e>,
}
複製代碼
到這裏變量對象的建立過程就介紹完了,讓咱們簡短地總結一下:
Arguments
對象若是以爲文章對你有些許幫助,歡迎在個人GitHub博客點贊和關注,感激涕零!