由一道題圖解JavaScript的做用域

做用域

爲了理解做用域,跪看了好幾篇大神的博文,終於略知一二。javascript

1.題目

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

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');

運行結果是:前端

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

雖然在不理解做用域的狀況下,我也作出了正確答案。但當我看了網上大神對做用域的講解後,再來看此題,越看越經典,當初能作對,純粹運氣。當我將此題的做用域鏈畫出來以後,終於感受做用域入門了。java

2.圖解做用域

(1)JS引擎在進入一段可執行的代碼時,須要完成如下三個初始化工做:web

  • 建立一個全局對象(Global Object):全局對象在建立時,將Math,String,Date,document 等經常使用的JS對象做爲其屬性。還有另一個屬性window,將window指向了自身,這樣就能夠經過window訪問這個全局對象了。app

  • 構建一個執行環境棧( Execution Context Stack):當執行一個函數時,該函數的執行環境就會被推入執行環境棧的頂部並獲取執行權。當這個函數執行完畢,它的執行環境又從這個棧的頂部被刪除,並把執行權並還給以前執行環境。函數

  • 建立一個全局執行環境(Execution Context)EC,並將這個全局執行環境EC壓入執行環境棧中。this

  • 建立一個與EC關聯的全局變量對象(Varibale Object) VO,並把VO指向全局對象。VO中不只包含了全局對象的原有屬性,還包括在全局定義的變量和函數。同時,在定義函數A的時候,還爲 A 添加了一個內部屬性scope,並將scope指向了VO。每一個函數在定義的時候,都會建立一個與之關聯的scope屬性,scope老是指向定義函數時所在的環境。
    圖片描述spa

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

  • 建立函數app的執行環境EC(a):而後EC(a)推入執行環境棧的頂部並獲取執行權。

  • 建立函數app的做用域鏈(Scope Chain):在javascript中,每一個執行環境都有本身的做用域鏈,用於標識符解析,當執行環境被建立時,它的做用域鏈就初始化爲當前運行函數的scope所包含的對象。

  • 建立一個當前函數的活動對象(Activation Object) AO:AO中包含了函數的形參、arguments對象、this對象、以及局部變量和內部函數的定義,而後AO會被推入做用域鏈的頂端。須要注意的是,在定義函數B的時候,JS引擎一樣也會爲B添加了一個scope屬性,並將scope指向了定義函數B時所在的環境,定義函數B的環境就是A的活動對象AO, 而AO位於鏈表的前端,因爲鏈表具備首尾相連的特色,所以函數B的scope指向了A的整個做用域鏈。
    圖片描述

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

  • 建立函數factory的執行環境EC(f):而後EC(f)推入執行環境棧的頂部並獲取執行權。

  • 建立函數factory的做用域鏈Scope Chain(f):在javascript中,每一個執行環境都有本身的做用域鏈,用於標識符解析,當執行環境被建立時,它的做用域鏈就初始化爲當前運行函數的scope所包含的對象。

  • 建立一個當前函數的活動對象AO(f):AO中包含了函數的形參、arguments對象、this對象、以及局部變量和內部函數的定義,而後AO(f)會被推入做用域鏈的頂端。須要注意的是,在定義函數intro的時候,JS引擎一樣也會爲intro添加了一個scope屬性,並將scope指向了定義函數intro時所在的環境,定義函數intro的環境就是factory的活動對象AO(f), 而AO(f)位於鏈表的前端,因爲鏈表具備首尾相連的特色,所以函數intro的scope指向了factory的整個做用域鏈。
    圖片描述

(4)當執行進入func()時
函數factory被執行之後,返回了intro的引用,並賦值給了變量func,執行 func() 就至關於執行intro()。

  • 建立函數intro的執行環境EC(i),而後EC(i)推入執行環境棧的頂部並獲取執行權。 此時執行環境棧中有三個執行環境,分別是全局執行環境、函數app的執行環境和函數intro,intro的執行環境在棧頂,全局執行環境在棧的底部。

  • 建立函數intro的做用域鏈Scope Chain(i),並初始化爲函數intro的scope所包含的對象,即包含了factory的做用域鏈。

  • 建立函數intro的活動對象AO(i),並將intro的arguments對象和this對象做爲AO(i)的屬性。
    圖片描述

執行func()時,它的做用域鏈爲 VO(G ) <- AO(f) <- AO(i),因此

  • 讀取name屬性時,AO(i)對象上沒有name屬性,沿着做用域鏈到AO(f),讀取到了name屬性,值爲"laruence";

  • 讀取age屬性時,其實你會發現只有AO(f)上定義了age屬性,而AO(f)又不在做用域鏈上,因此此時會報腳本錯誤Uncaught ReferenceError: age is not defined,表示age屬性未定義。這也從側面證實了咱們上面的做用域鏈分析是正確的。

執行完成,退出。

3.Javascript中的做用域

  • JavaScript中的函數運行在它們被定義的做用域裏,而不是它們被執行的做用域裏。

  • 在JS中,每次調用一個函數的時候 ,就會進入一個函數內的做用域,當從函數返回之後,就返回調用前的做用域。

  • 在javascript中,每一個函數都有本身的執行環境,當執行一個函數時,該函數的執行環境就會被推入執行環境棧的頂部並獲取執行權。當這個函數執行完畢,它的執行環境又從這個棧的頂部被刪除,並把執行權並還給以前執行環境。

  • 每一個函數在定義的時候,都會建立一個與之關聯的scope屬性,scope老是指向定義函數時所在的環境。

  • 在javascript中,每一個執行環境都有本身的做用域鏈,用於標識符解析,當執行環境被建立時,它的做用域鏈就初始化爲當前運行函數的scope所包含的對象。

  • 有了做用域鏈, 在發生標識符解析的時候, 就會逆向查詢當前scope chain列表的每個活動對象的屬性,若是找到同名的就返回。找不到,那就是這個標識符沒有被定義。

  • 全局對象(Global Object) , 這個對象全局只存在一份,它的屬性在任何地方均可以訪問,它的存在伴隨着應用程序的整個生命週期。全局對象在建立時,Math,String,Date,document 等經常使用的JS對象做爲其屬性。

  • 活動對象(Activation Object) AO,這裏的活動對象扮演着變量對象的角色,只是在函數中的叫法不一樣而已(你能夠認爲變量對象是一個總的概念,而活動對象是它的一個分支), AO中包含了函數的形參、arguments對象、this對象、以及局部變量和內部函數的定義,而後AO會被推入做用域鏈的頂端。

參考文獻

相關文章
相關標籤/搜索