作用域
為了理解作用域,跪看了好幾篇大神的博文,終于略知一二。
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會被推入作用域鏈的頂端。