JavaScript深刻之變量對象(轉載)

前言

在上篇《JavaScript深刻之執行上下文棧》中講到,當 JavaScript 代碼執行一段可執行代碼(executable code)時,會建立對應的執行上下文(execution context)。git

對於每一個執行上下文,都有三個重要屬性:github

  • 變量對象(Variable object,VO)
  • 做用域鏈(Scope chain)
  • this

今天重點講講建立變量對象的過程。數組

變量對象

變量對象是與執行上下文相關的數據做用域,存儲了在上下文中定義的變量和函數聲明。dom

由於不一樣執行上下文下的變量對象稍有不一樣,因此咱們來聊聊全局上下文下的變量對象和函數上下文下的變量對象。函數

全局上下文

咱們先了解一個概念,叫全局對象。在 W3School 中也有介紹:this

全局對象是預約義的對象,做爲 JavaScript 的全局函數和全局屬性的佔位符。經過使用全局對象,能夠訪問全部其餘全部預約義的對象、函數和屬性。url

在頂層 JavaScript 代碼中,能夠用關鍵字 this 引用全局對象。由於全局對象是做用域鏈的頭,這意味着全部非限定性的變量和函數名都會做爲該對象的屬性來查詢。spa

例如,當JavaScript 代碼引用 parseInt() 函數時,它引用的是全局對象的 parseInt 屬性。全局對象是做用域鏈的頭,還意味着在頂層 JavaScript 代碼中聲明的全部變量都將成爲全局對象的屬性。code

若是看的不是很懂的話,容我再來介紹下全局對象:對象

1.能夠經過 this 引用,在客戶端 JavaScript 中,全局對象就是 Window 對象。

console.log(this);

2.全局對象是由 Object 構造函數實例化的一個對象。

console.log(this instanceof Object);

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" }

到這裏變量對象的建立過程就介紹完了,讓咱們簡潔的總結咱們上述所說:

  1. 全局上下文的變量對象初始化是全局對象

  2. 函數上下文的變量對象初始化只包括 Arguments 對象

  3. 在進入執行上下文時會給變量對象添加形參、函數聲明、變量聲明等初始的屬性值

  4. 在代碼執行階段,會再次修改變量對象的屬性值

思考題

最後讓咱們看幾個例子:

1.第一題

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。

2.第二題

console.log(foo); function foo(){ console.log("foo"); } var foo = 1;

會打印函數,而不是 undefined 。

這是由於在進入執行上下文時,首先會處理函數聲明,其次會處理變量聲明,若是若是變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會干擾已經存在的這類屬性。

Arguments對象:調用函數時,會爲其建立一個Arguments對象,並自動初始化局部變量arguments,指代該Arguments對象。全部做爲參數傳入的值都會成爲Arguments對象的數組元素。

VO 和 AO 關係:

1樓:未進入執行階段以前,變量對象(VO)中的屬性都不能訪問!可是進入執行階段以後,變量對象(VO)轉變爲了活動對象(AO),裏面的屬性都能被訪問了,而後開始進行執行階段的操做。

它們其實都是同一個對象,只是處於執行上下文的不一樣生命週期

2樓:1樓說的有一點偏差,AO 其實是包含了 VO 的。由於除了 VO 以外,AO 還包含函數的 parameters,以及 arguments 這個特殊對象。也就是說 AO 的確是在進入到執行階段的時候被激活,可是激活的除了 VO 以外,還包括函數執行時傳入的參數和 arguments 這個特殊對象。
AO = VO + function parameters + arguments

原文網址:https://github.com/mqyqingfeng/Blog/issues/5

相關文章
相關標籤/搜索