變量對(duì)象

參考

JavaScript深入之變量對(duì)象
深入理解JavaScript系列(12):變量對(duì)象(Variable Object)

前言


  1. 對(duì)于每個(gè)執(zhí)行上下文,都有三個(gè)重要屬性

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

今天重點(diǎn)講講創(chuàng)建變量對(duì)象的過程。

變量對(duì)象


變量對(duì)象是與執(zhí)行上下文相關(guān)的數(shù)據(jù)作用域,存儲(chǔ)了在上下文中定義的變量和函數(shù)聲明

因?yàn)椴煌瑘?zhí)行上下文下的變量對(duì)象稍有不同,所以我們來聊聊全局上下文下的變量對(duì)象和函數(shù)上下文下的變量對(duì)象。

全局上下文


我們先了解一個(gè)概念,叫全局對(duì)象。在 W3School 中也有介紹:

全局對(duì)象是預(yù)定義的對(duì)象,作為 JavaScript 的全局函數(shù)和全局屬性的占位符。通過使用全局對(duì)象,可以訪問所有其他所有預(yù)定義的對(duì)象、函數(shù)和屬性

在頂層 JavaScript 代碼中,可以用關(guān)鍵字 this 引用全局對(duì)象。因?yàn)槿謱?duì)象是作用域鏈的頭,這意味著所有非限定性的變量和函數(shù)名都會(huì)作為該對(duì)象的屬性來查詢。

例如,當(dāng)JavaScript 代碼引用 parseInt() 函數(shù)時(shí),它引用的是全局對(duì)象的 parseInt 屬性。全局對(duì)象是作用域鏈的頭,還意味著在頂層 JavaScript 代碼中聲明的所有變量都將成為全局對(duì)象的屬性。

如果看的不是很懂的話,容我再來介紹下全局對(duì)象:
1.可以通過 this 引用,在客戶端 JavaScript 中,全局對(duì)象就是 Window 對(duì)象。

console.log(this);
  1. 全局對(duì)象繼承了 Object

3.預(yù)定義了一堆,嗯,一大堆函數(shù)和屬性。

// 都能生效
console.log(Math.random());
console.log(this.Math.random());

4.作為全局變量的宿主。

var a = 1;
console.log(this.a);

5.客戶端 JavaScript 中,全局對(duì)象有 window 屬性指向自身。

var a = 1;
console.log(window.a);

this.window.b = 2;  // window 屬性指向自身
console.log(this.b); //2

花了一個(gè)大篇幅介紹全局對(duì)象,其實(shí)就想說:

全局上下文中的變量對(duì)象就是全局對(duì)象吶!

函數(shù)上下文


在函數(shù)上下文中,我們用活動(dòng)對(duì)象(activation object, AO)來表示變量對(duì)象

活動(dòng)對(duì)象和變量對(duì)象其實(shí)是一個(gè)東西,只是變量對(duì)象是規(guī)范上的或者說是引擎實(shí)現(xiàn)上的,不可在 JavaScript 環(huán)境中訪問,當(dāng)進(jìn)入一個(gè)函數(shù)上下文中,這個(gè)函數(shù)上下文的變量對(duì)象才會(huì)被激活,所以才叫 activation object,而只有被激活的變量對(duì)象,也就是活動(dòng)對(duì)象上的各種屬性才能被訪問。

活動(dòng)對(duì)象是在進(jìn)入函數(shù)上下文時(shí)刻被創(chuàng)建的,它通過函數(shù)的 arguments 屬性初始化。arguments 屬性值是 Arguments 對(duì)象。

執(zhí)行過程


執(zhí)行上下文的代碼會(huì)分成兩個(gè)階段進(jìn)行處理:分析和執(zhí)行,我們也可以叫做:

  1. 進(jìn)入執(zhí)行上下文
  2. 代碼執(zhí)行

進(jìn)入執(zhí)行上下文


當(dāng)進(jìn)入執(zhí)行上下文時(shí),這時(shí)候還沒有執(zhí)行代碼,

變量對(duì)象會(huì)包括:

  1. 函數(shù)的所有形參 (如果是函數(shù)上下文)

    • 由名稱和對(duì)應(yīng)值組成的一個(gè)變量對(duì)象的屬性被創(chuàng)建
    • 沒有實(shí)參,屬性值設(shè)為 undefined
  2. 函數(shù)聲明

    • 由名稱和對(duì)應(yīng)值(函數(shù)對(duì)象(function-object))組成一個(gè)變量對(duì)象的屬性被創(chuàng)建
    • 如果變量對(duì)象已經(jīng)存在相同名稱的屬性,則完全替換這個(gè)屬性
  3. 變量聲明

    • 由名稱和對(duì)應(yīng)值(undefined)組成一個(gè)變量對(duì)象的屬性被創(chuàng)建;
    • 如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會(huì)干擾已經(jīng)存在的這類屬性

舉個(gè)例子:

function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};

  b = 3;

}

foo(1);

在進(jìn)入執(zhí)行上下文后,這時(shí)候的 AO 是:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}

代碼執(zhí)行


在代碼執(zhí)行階段,會(huì)順序執(zhí)行代碼,根據(jù)代碼,修改變量對(duì)象的值

還是上面的例子,當(dāng)代碼執(zhí)行完后,這時(shí)候的 AO 是:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 3,
    c: reference to function c(){},
    d: reference to FunctionExpression "d"
}

到這里變量對(duì)象的創(chuàng)建過程就介紹完了,讓我們簡(jiǎn)潔的總結(jié)我們上述所說:

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

  2. 函數(shù)上下文的變量對(duì)象初始化只包括 Arguments 對(duì)象

  3. 在進(jìn)入執(zhí)行上下文時(shí)會(huì)給變量對(duì)象添加形參、函數(shù)聲明、變量聲明等初始的屬性值

  4. 在代碼執(zhí)行階段,會(huì)再次修改變量對(duì)象的屬性值

思考題


1.第一題

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

foo(); // ???

function bar() {
    a = 1;
    console.log(a);
}
bar(); // ???

第一段會(huì)報(bào)錯(cuò):Uncaught ReferenceError: a is not defined

第二段會(huì)打?。?code>1。

這是因?yàn)楹瘮?shù)中的 "a" 并沒有通過 var 關(guān)鍵字聲明,所有不會(huì)被存放在 AO 中。
第一段執(zhí)行 console 的時(shí)候, AO 的值是:

AO = {
    arguments: {
        length: 0
    }
}

沒有 a 的值,然后就會(huì)到全局去找,全局也沒有,所以會(huì)報(bào)錯(cuò)。

當(dāng)?shù)诙螆?zhí)行 console 的時(shí)候,全局對(duì)象已經(jīng)被賦予了 a 屬性,這時(shí)候就可以從全局找到 a 的值,所以會(huì)打印 1。

2.第二題

console.log(foo);

function foo(){
    console.log("foo");
}

var foo = 1;

會(huì)打印函數(shù),而不是 undefined 。

這是因?yàn)樵谶M(jìn)入執(zhí)行上下文時(shí),首先會(huì)處理函數(shù)聲明,其次會(huì)處理變量聲明,如果如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會(huì)干擾已經(jīng)存在的這類屬性。

3.第三題

var foo = function () {

    console.log('foo1');

}

foo();  // foo1

var foo = function () {

    console.log('foo2');

}

foo(); // foo2

這是因?yàn)樵谶M(jìn)入全局上下文后,這時(shí)的 VO 是:

AO = {
    foo: undefined,
}

在代碼執(zhí)行階段,會(huì)順序執(zhí)行代碼,根據(jù)代碼,修改變量對(duì)象中 foo 的指向。

  1. 第四題
function foo() {

    console.log('foo1');

}

foo();  // foo2

function foo() {

    console.log('foo2');

}

foo(); // foo2

這是因?yàn)樵谶M(jìn)入全局上下文后,在 VO 中后面的函數(shù)聲明會(huì)覆蓋掉前面的函數(shù)聲明

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • (注1:如果有問題歡迎留言探討,一起學(xué)習(xí)!轉(zhuǎn)載請(qǐng)注明出處,喜歡可以點(diǎn)個(gè)贊哦?。ㄗ?:更多內(nèi)容請(qǐng)查看我的目錄。) ...
    love丁酥酥閱讀 786評(píng)論 1 3
  • 上一節(jié)我們討論了執(zhí)行上下文,那么在上下文中到底有什么內(nèi)容,為什么它會(huì)和作用域鏈扯上關(guān)系,JS 解釋器又是怎么找到我...
    liuxuan閱讀 632評(píng)論 0 3
  • 執(zhí)行上下文 可執(zhí)行代碼JavaScript中可執(zhí)行代碼(executable code)分為三種:全局代碼,函數(shù)代...
    LeoCong閱讀 367評(píng)論 0 0
  • 寫在前面的話 接觸前端到現(xiàn)在大概也有三個(gè)多月了,越深入學(xué)習(xí)越發(fā)現(xiàn)自己理解的學(xué)到的只是一知半解。于是翻閱了許多大神如...
    Promise__閱讀 686評(píng)論 0 3
  • 太陽升起來了,看來今天是個(gè)大晴天。欣欣和田田出到大門外,也感受到了。 欣欣說:“昨天在樹上,我...
    以科_da36閱讀 440評(píng)論 0 1

友情鏈接更多精彩內(nèi)容