由一道題圖解JavaScript的作用域

作用域

為了理解作用域,跪看了好幾篇大神的博文,終于略知一二。

1.題目

其中,看到這樣一道題(稍作修改):

 function factory() {
     var name = 'laruence';
     var intro = function(){
          console.log('I am ' + name);
          console.log('I am ' + age + "years");
     }
     return intro;
}
 
function app(para){
     var name = para;
     var age = 20;
     var func = factory();
     func();
}
 
app('eve');

運行結(jié)果是:

I am laruence
腳本報錯:Uncaught ReferenceError: age is not defined

雖然在不理解作用域的情況下,我也做出了正確答案。但當我看了網(wǎng)上大神對作用域的講解后,再來看此題,越看越經(jīng)典,當初能做對,純粹運氣。當我將此題的作用域鏈畫出來之后,終于感覺作用域入門了。

2.圖解作用域

(1)JS引擎在進入一段可執(zhí)行的代碼時,需要完成以下三個初始化工作:

  • 創(chuàng)建一個全局對象(Global Object):全局對象在創(chuàng)建時,將Math,String,Date,document 等常用的JS對象作為其屬性。還有另外一個屬性window,將window指向了自身,這樣就可以通過window訪問這個全局對象了。
  • 構(gòu)建一個執(zhí)行環(huán)境棧( Execution Context Stack):當執(zhí)行一個函數(shù)時,該函數(shù)的執(zhí)行環(huán)境就會被推入執(zhí)行環(huán)境棧的頂部并獲取執(zhí)行權(quán)。當這個函數(shù)執(zhí)行完畢,它的執(zhí)行環(huán)境又從這個棧的頂部被刪除,并把執(zhí)行權(quán)并還給之前執(zhí)行環(huán)境。
  • 創(chuàng)建一個全局執(zhí)行環(huán)境(Execution Context)EC,并將這個全局執(zhí)行環(huán)境EC壓入執(zhí)行環(huán)境棧中。
  • 創(chuàng)建一個與EC關聯(lián)的全局變量對象(Varibale Object) VO,并把VO指向全局對象。VO中不僅包含了全局對象的原有屬性,還包括在全局定義的變量和函數(shù)。同時,在定義函數(shù)A的時候,還為 A 添加了一個內(nèi)部屬性scope,并將scope指向了VO。每個函數(shù)在定義的時候,都會創(chuàng)建一個與之關聯(lián)的scope屬性,scope總是指向定義函數(shù)時所在的環(huán)境。
    圖片描述
    圖片描述

(2)當執(zhí)行進入app('eve') 時

  • 創(chuàng)建函數(shù)app的執(zhí)行環(huán)境EC(a):然后EC(a)推入執(zhí)行環(huán)境棧的頂部并獲取執(zhí)行權(quán)。
  • 創(chuàng)建函數(shù)app的作用域鏈(Scope Chain):在javascript中,每個執(zhí)行環(huán)境都有自己的作用域鏈,用于標識符解析,當執(zhí)行環(huán)境被創(chuàng)建時,它的作用域鏈就初始化為當前運行函數(shù)的scope所包含的對象。
  • 創(chuàng)建一個當前函數(shù)的活動對象(Activation Object) AO:AO中包含了函數(shù)的形參、arguments對象、this對象、以及局部變量和內(nèi)部函數(shù)的定義,然后AO會被推入作用域鏈的頂端。需要注意的是,在定義函數(shù)B的時候,JS引擎同樣也會為B添加了一個scope屬性,并將scope指向了定義函數(shù)B時所在的環(huán)境,定義函數(shù)B的環(huán)境就是A的活動對象AO, 而AO位于鏈表的前端,由于鏈表具有首尾相連的特點,因此函數(shù)B的scope指向了A的整個作用域鏈。


    圖片描述
    圖片描述

(3)當執(zhí)行進入factory() 時

  • 創(chuàng)建函數(shù)factory的執(zhí)行環(huán)境EC(f):然后EC(f)推入執(zhí)行環(huán)境棧的頂部并獲取執(zhí)行權(quán)。
  • 創(chuàng)建函數(shù)factory的作用域鏈Scope Chain(f):在javascript中,每個執(zhí)行環(huán)境都有自己的作用域鏈,用于標識符解析,當執(zhí)行環(huán)境被創(chuàng)建時,它的作用域鏈就初始化為當前運行函數(shù)的scope所包含的對象。
  • 創(chuàng)建一個當前函數(shù)的活動對象AO(f):AO中包含了函數(shù)的形參、arguments對象、this對象、以及局部變量和內(nèi)部函數(shù)的定義,然后AO(f)會被推入作用域鏈的頂端。需要注意的是,在定義函數(shù)intro的時候,JS引擎同樣也會為intro添加了一個scope屬性,并將scope指向了定義函數(shù)intro時所在的環(huán)境,定義函數(shù)intro的環(huán)境就是factory的活動對象AO(f), 而AO(f)位于鏈表的前端,由于鏈表具有首尾相連的特點,因此函數(shù)intro的scope指向了factory的整個作用域鏈。


    圖片描述
    圖片描述

(4)當執(zhí)行進入func()時
函數(shù)factory被執(zhí)行以后,返回了intro的引用,并賦值給了變量func,執(zhí)行 func() 就相當于執(zhí)行intro()。

  • 創(chuàng)建函數(shù)intro的執(zhí)行環(huán)境EC(i),然后EC(i)推入執(zhí)行環(huán)境棧的頂部并獲取執(zhí)行權(quán)。 此時執(zhí)行環(huán)境棧中有三個執(zhí)行環(huán)境,分別是全局執(zhí)行環(huán)境、函數(shù)app的執(zhí)行環(huán)境和函數(shù)intro,intro的執(zhí)行環(huán)境在棧頂,全局執(zhí)行環(huán)境在棧的底部。
  • 創(chuàng)建函數(shù)intro的作用域鏈Scope Chain(i),并初始化為函數(shù)intro的scope所包含的對象,即包含了factory的作用域鏈。
  • 創(chuàng)建函數(shù)intro的活動對象AO(i),并將intro的arguments對象和this對象作為AO(i)的屬性。


    圖片描述
    圖片描述

執(zhí)行func()時,它的作用域鏈為 VO(G ) <- AO(f) <- AO(i),所以

  • 讀取name屬性時,AO(i)對象上沒有name屬性,沿著作用域鏈到AO(f),讀取到了name屬性,值為"laruence";
  • 讀取age屬性時,其實你會發(fā)現(xiàn)只有AO(f)上定義了age屬性,而AO(f)又不在作用域鏈上,所以此時會報腳本錯誤Uncaught ReferenceError: age is not defined,表示age屬性未定義。這也從側(cè)面證明了我們上面的作用域鏈分析是正確的。

執(zhí)行完成,退出。

3.Javascript中的作用域

  • JavaScript中的函數(shù)運行在它們被定義的作用域里,而不是它們被執(zhí)行的作用域里。
  • 在JS中,每次調(diào)用一個函數(shù)的時候 ,就會進入一個函數(shù)內(nèi)的作用域,當從函數(shù)返回以后,就返回調(diào)用前的作用域。
  • 在javascript中,每個函數(shù)都有自己的執(zhí)行環(huán)境,當執(zhí)行一個函數(shù)時,該函數(shù)的執(zhí)行環(huán)境就會被推入執(zhí)行環(huán)境棧的頂部并獲取執(zhí)行權(quán)。當這個函數(shù)執(zhí)行完畢,它的執(zhí)行環(huán)境又從這個棧的頂部被刪除,并把執(zhí)行權(quán)并還給之前執(zhí)行環(huán)境。
  • 每個函數(shù)在定義的時候,都會創(chuàng)建一個與之關聯(lián)的scope屬性,scope總是指向定義函數(shù)時所在的環(huán)境。
  • 在javascript中,每個執(zhí)行環(huán)境都有自己的作用域鏈,用于標識符解析,當執(zhí)行環(huán)境被創(chuàng)建時,它的作用域鏈就初始化為當前運行函數(shù)的scope所包含的對象。
  • 有了作用域鏈, 在發(fā)生標識符解析的時候, 就會逆向查詢當前scope chain列表的每一個活動對象的屬性,如果找到同名的就返回。找不到,那就是這個標識符沒有被定義。
  • 全局對象(Global Object) , 這個對象全局只存在一份,它的屬性在任何地方都可以訪問,它的存在伴隨著應用程序的整個生命周期。全局對象在創(chuàng)建時,Math,String,Date,document 等常用的JS對象作為其屬性。
  • 活動對象(Activation Object) AO,這里的活動對象扮演著變量對象的角色,只是在函數(shù)中的叫法不同而已(你可以認為變量對象是一個總的概念,而活動對象是它的一個分支), AO中包含了函數(shù)的形參、arguments對象、this對象、以及局部變量和內(nèi)部函數(shù)的定義,然后AO會被推入作用域鏈的頂端。

參考文獻

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

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

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