深入學習js系列是自己階段性成長的見證,希望通過文章的形式更加嚴謹、客觀地梳理js的相關知識,也希望能夠幫助更多的前端開發的朋友解決問題,期待我們的共同進步。
如果覺得本系列不錯,歡迎點贊、評論、轉發,您的支援就是我堅持的最大動力。
開篇
上一篇文章重點介紹的是執行山下文棧,當javascript程式碼執行一段可執行的程式碼(executable code)時候,會建立對應的執行上下文(execution context)。
對於每一個執行上下文,都有三個重要的屬性:
- 變數物件(Variable object VO)
- 作用域鏈(Scope chain)
- this
本文重點介紹建立變數物件的過程。
變數物件
變數物件是與執行上下文相關的資料作用域,儲存了再上下文中定義的變數和函式宣告。
因為不同執行山下文下的變數物件稍有不同,所以我們來聊聊全域性上下文下的變數物件和函式上下文下的變數物件。
全域性上下文
我們先了解一個概念,叫做全域性物件。在W3CSchool中也有介紹:
全域性物件是預定義的物件,作為 JavaScript 的全域性函式和全域性屬性的佔位符。通過使用全域性物件,可以訪問所有其他所有預定義的物件、函式和屬性。
在頂層 JavaScript 程式碼中,可以用關鍵字 this 引用全域性物件。因為全域性物件是作用域鏈的頭,這意味著所有非限定性的變數和函式名都會作為該物件的屬性來查詢。
例如,當JavaScript 程式碼引用 parseInt() 函式時,它引用的是全域性物件的 parseInt 屬性。全域性物件是作用域鏈的頭,還意味著在頂層 JavaScript 程式碼中宣告的所有變數都將成為全域性物件的屬性。
如果感覺全域性物件有些看不懂的話,容我再來介紹一下全域性物件:
1.可以通過this引用,在客戶端javascript中,全域性物件就是window物件。
console.log(this); // window
複製程式碼
2.全域性物件是由Object 建構函式例項化的一個物件。
console.log(this instanceof Object); // true
複製程式碼
3.預定義一堆,恩,一大堆的函式和屬性。
// /都能生效
console.log(Math.random());
console.log(this.Math.random());
複製程式碼
4.作為全域性變數的宿主。
var a = 1;
console.log(this.a);
複製程式碼
5.客戶端javascript中,全域性物件window屬性指向自身
var a = 1;
console.log(window.a);
this.window.b = 2;
console.log(this.b);
複製程式碼
花了一個大篇幅介紹全域性物件,其實就是想說:
全域性上下文中的變數物件就是全域性物件吶!
函式執行上下文
在函式上下文中,我們用活動物件(activation object,AO)
活動物件和變數物件其實是一個東西,只是變數物件是規範上的或者是引擎實現的,不可在javascript環境中訪問,只有進入一個執行上下文中,這個執行上下文的變數物件才會被啟用,所以才叫做 activation object,而只有被啟用的變數物件,也就是活動物件上的各種屬性才能被訪問。
活動物件是在進入函式上下文時刻被建立的,它通過函式的arguments屬性初始化,arguments屬性值是Arguments物件。
執行過程
執行上下文的程式碼會分成兩個階段進行處理:分析和執行,我們也可以叫做: 1、進入執行山下文 2、程式碼執行
進入執行上下文
進入執行上下文的時候,這個時候還沒有執行程式碼,
變數物件包括: 1、函式的所有形參(如何是函式上下文)
* 由名稱和對應的值組成一個變數物件的屬性被建立
* 沒有實參,屬性值設定為 undefined
複製程式碼
2、函式宣告
* 由名稱和對應值(函式物件(function-object))組成一個變數物件的屬性被建立
* 如果變數物件已經存在相同名稱的屬性,則完全替換這個屬性
複製程式碼
3、變數宣告
* 由名稱和對應值(undefined)組成一個變數物件的屬性被建立;
* 如果變數名稱跟已經宣告的形式引數或函式相同,則變數宣告不會干擾已經存在的這類屬性
複製程式碼
舉個例子:
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);
複製程式碼
在進入執行上下文後,這時候的 AO 是
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
複製程式碼
程式碼執行
在程式碼執行階段,會順序的執行程式碼,根據程式碼,修改變數物件的值
這是上面的例子,當程式碼執行完後,這時的AO是:
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: 3,
c: reference to function c(){},
d: reference to FunctionExpression "d"
}
複製程式碼
到這裡變數物件的建立過程就介紹完了,讓我們簡潔的總結我們上述所說:
- 全域性上下文的變數物件初始化是全域性物件
- 函式上下文的變數物件初始化只包括 Arguments 物件
- 在進入執行上下文時會給變數物件新增形參、函式宣告、變數宣告等初始的屬性值
- 在程式碼執行階段,會再次修改變數物件的屬性值
思考題
最後我們看幾個例子:
第一題:
function foo(){
console.log(a);
a = 1;
}
foo(); // ??
function bar(){
a = 1;
console.log(a);
}
bar(); // ??
複製程式碼
第一段會報錯:Uncaught ReferenceError: a is not defined
。
第二段會列印:1
。
這是因為函式中的 "a" 並沒有通過 var 關鍵字宣告,所有不會被存放在 AO 中。
第一段執行 console 的時候, AO 的值是:
AO = {
arguments: {
length: 0
}
}
複製程式碼
沒有 a 的值,然後就會到全域性去找,全域性也沒有,所以會報錯。
當第二段執行 console 的時候,全域性物件已經被賦予了 a 屬性,這時候就可以從全域性找到 a 的值,所以會列印 1。
第二題:
console.log(foo);
function foo(){
console.log("foo");
}
var foo = 1;
複製程式碼
會列印函式,而不是 undefined 。
這是因為在進入執行上下文時,首先會處理函式宣告,其次會處理變數宣告,如果如果變數名稱跟已經宣告的形式引數或函式相同,則變數宣告不會干擾已經存在的這類屬性。
深入學習JavaScript系列目錄
- #1 【深入學習js之——原型和原型鏈】
- #2 【深入學習js之——詞法作用域和動態作用域】
- #3 【深入學習js之——執行山下文棧】
- #4 【深入學習js之——變數物件】
- #5 【深入學習js之——作用域鏈】
- #6 【深入學習js之——實際開發場景中的this指向】
- #7 【深入學習js之——執行上下文】
- #8 【深入學習js之——閉包】
- #9 【深入學習js之——引數按值傳遞】
歡迎新增我的個人微信討論技術和個體成長。
歡迎關注我的個人微信公眾號——指尖的宇宙,更多優質思考乾貨