爲了理解做用域,跪看了好幾篇大神的博文,終於略知一二。javascript
其中,看到這樣一道題(稍做修改):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
(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
屬性未定義。這也從側面證實了咱們上面的做用域鏈分析是正確的。
執行完成,退出。
JavaScript中的函數運行在它們被定義的做用域裏,而不是它們被執行的做用域裏。
在JS中,每次調用一個函數的時候 ,就會進入一個函數內的做用域,當從函數返回之後,就返回調用前的做用域。
在javascript中,每一個函數都有本身的執行環境,當執行一個函數時,該函數的執行環境就會被推入執行環境棧的頂部並獲取執行權。當這個函數執行完畢,它的執行環境又從這個棧的頂部被刪除,並把執行權並還給以前執行環境。
每一個函數在定義的時候,都會建立一個與之關聯的scope屬性,scope老是指向定義函數時所在的環境。
在javascript中,每一個執行環境都有本身的做用域鏈,用於標識符解析,當執行環境被建立時,它的做用域鏈就初始化爲當前運行函數的scope所包含的對象。
有了做用域鏈, 在發生標識符解析的時候, 就會逆向查詢當前scope chain列表的每個活動對象的屬性,若是找到同名的就返回。找不到,那就是這個標識符沒有被定義。
全局對象(Global Object) , 這個對象全局只存在一份,它的屬性在任何地方均可以訪問,它的存在伴隨着應用程序的整個生命週期。全局對象在建立時,Math,String,Date,document 等經常使用的JS對象做爲其屬性。
活動對象(Activation Object) AO,這裏的活動對象扮演着變量對象的角色,只是在函數中的叫法不一樣而已(你能夠認爲變量對象是一個總的概念,而活動對象是它的一個分支), AO中包含了函數的形參、arguments對象、this對象、以及局部變量和內部函數的定義,而後AO會被推入做用域鏈的頂端。