深入理解閉包(二)——變量對象

原文地址:深入理解閉包(二)——變量對象

執(zhí)行一個函數(shù)之前,JavaScript引擎會進(jìn)行準(zhǔn)備工作,這個準(zhǔn)備工作指的就是執(zhí)行上下文,也叫執(zhí)行上下文環(huán)境,也叫執(zhí)行環(huán)境。
下面是一個執(zhí)行上下文的周期圖,其中變量對象(Variable Object)是重點之一,只有理解了它,我們才能知道一段代碼的執(zhí)行過程中先做什么,后做什么,我們今天就探討一下變量對象在執(zhí)行上下文生命周期中都經(jīng)歷了怎樣的變化:

創(chuàng)建階段

  1. 建立arguments對象(參數(shù)對象,當(dāng)某個函數(shù)接收參數(shù)的時候,會將參數(shù)封裝成arguments對象)。
  2. 檢查當(dāng)前上下文的函數(shù)聲明,也就是使用function聲明的函數(shù)。在變量對象中以函數(shù)名建立一個屬性,屬性值為指向該函數(shù)所在的內(nèi)存地址的引用。如果函數(shù)名的屬性已存在,那么該屬性將會被新的引用所覆蓋。
  3. 檢查當(dāng)前上下文的變量聲明,也就是使用var聲明的變量,對于每一個變量聲明,變量對象會以變量名建立一個屬性,屬性值為undefined。函數(shù)的聲明比變量優(yōu)先級要高,并且定義過程不會被變量覆蓋,除非是賦值。

執(zhí)行階段

執(zhí)行階段沒有什么難以理解的,有兩點需要知道一下:

  1. 代碼執(zhí)行時按照順序,該賦值時賦值,該調(diào)用時調(diào)用,這個一看后面的栗子就明白了。
  2. 變量對象轉(zhuǎn)變?yōu)榱嘶顒訉ο螅ˋctive Object),其實這倆是一個東西,只是所處的時期不同罷了。

栗子們

栗子1:

console.log(a);  //a is not defined

上面代碼執(zhí)行環(huán)境中根本沒有定義變量a

console.log(a);  //undefined
var a = 10;

雖然同樣沒有打印a的值,但是跟之前不同的是,這里執(zhí)行環(huán)境中查找到了變量a,但是在打印時它還沒有進(jìn)行賦值,因為查找變量a是在執(zhí)行上下文創(chuàng)建階段完成的,并定義為undefined,然后才進(jìn)入執(zhí)行階段,但是打印a的語句在前面,賦值語句在后面,所以打印出來的是a的初始值undefined,這也就體現(xiàn)了前面說的執(zhí)行階段是按照順序執(zhí)行相關(guān)賦值、打印、函數(shù)調(diào)用等代碼。我們可以把上面代碼等效成下面的形式:

var a;
console.log(a);
a = 10;

栗子2:

console.log(test);     //function test(){ return 1;}
console.log(test());   //1   test是函數(shù),test()是函數(shù)調(diào)用之后返回的值
function test() {
    return 1;
}

上面代碼在創(chuàng)建階段檢查到的是函數(shù)聲明,因此可以順利打印函數(shù)。

console.log(test);      //undefined
console.log(test());    //test is not a function
var test = function () {
    return 1;
}

這段代碼也是要打印函數(shù),但是卻得到了截然不同的效果,因為這里的test其實是一個變量聲明,只不過給它賦值的是一個函數(shù),但是在創(chuàng)建階段它的初始值仍是undefined。

栗子3:

function test(x,y) {
    console.log(arguments);   //{ '0': 1, '1': 2 }
    console.log(a);           //undefined
    console.log(b);           //function func(){  return 2;  }
    console.log(b());         //2
    console.log(c);           //unundefined
    var a = 10;
    console.log(a);           //10
    function b() {
        return 1;
    }
    function b() {
        return 2;
    }
    var b;
    var c=function () {
        return 3;
    }
    console.log(c);          //function func(){  return 2;  }
    console.log(c());        //3
}
test(1,2);  
  • 檢查到函數(shù)調(diào)用時傳入了參數(shù)(1,2),以參數(shù)為屬性值封裝成arguments對象{ '0': 1, '1': 2 }。
  • 檢查函數(shù)聲明,先檢查到第一個b函數(shù),便以b為屬性名,b函數(shù)的內(nèi)存地址引用為屬性值建立一個屬性。但緊接著又檢查到一個b函數(shù),屬性值將會被后面的b函數(shù)覆蓋,因此打印b函數(shù)時得到的是后面聲明的b函數(shù)。
  • 檢查變量聲明,得到變量a,b,c,由于函數(shù)b的存在,b屬性已經(jīng)被創(chuàng)建了,并且這里的變量b并沒有賦值,因此它不會覆蓋屬性b的值,而是跳過。不過另外的變量a和c都順利創(chuàng)建屬性a和c,屬性值為undefined。
  • 按順序執(zhí)行代碼,第一次打印a時還沒有經(jīng)過賦值,因此輸出undefined,第二次打印a時已經(jīng)賦值,因此輸出10,其他同理,不再解釋。

如果按照執(zhí)行順序把栗子3代碼做個等效的話,是下面這樣的:

function test(x,y) {
    function b() {
        return 2;
    }
    var a;
    var c;
    console.log(arguments);   
    console.log(a);           
    console.log(b);          
    console.log(b());         
    console.log(c);          
    a = 10;
    console.log(a);          
    c=function () {
        return 3;
    }
    console.log(c);          
    console.log(c());        
}
test(1,2);  

結(jié)語

現(xiàn)在我猜你對于一段代碼的執(zhí)行過程應(yīng)該已經(jīng)了解得差不多了,你不要覺得這跟閉包看起來沒有關(guān)系就忽略這里的知識點,其實你對基礎(chǔ)的東西理解透徹,對后面難點的理解是潛移默化的,不僅僅是閉包,很多難點在夯實的基礎(chǔ)面前都是赤身裸體的,很容易就能看得一清二楚。今天關(guān)于變量對象就說到這里,下一次我們會進(jìn)一步探查執(zhí)行上下文的奧秘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,196評論 0 13
  • 王福朋 - 博客園 —— 《 深入理解javascript原型和閉包》 目錄:深入理解javascript原型和閉...
    帥而不花__美而不浪閱讀 1,488評論 0 2
  • 函數(shù)和對象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對于任何一門語言來說都是核心的概念。通過函數(shù)可以封裝任意多條語句,而且...
    道無虛閱讀 4,657評論 0 5
  • 橋頭上有一對養(yǎng)蜂的夫妻,男的五十歲左右,女的四十五六歲,每天早上在帳篷外放上音響,為過往的行人激情演唱,有時是男聲...
    善下歸海閱讀 431評論 0 4
  • 真的不騙你,上周末的幾檔綜藝,居然不小心成了教育課。首先,我要說明一下,我不是誰的粉絲,對明星也不是那么八...
    胖胖李閱讀 158評論 0 0