本篇文章參考了https://github.com/mqyqingfeng/Blog/issues/5 這位大佬的github。
js在執(zhí)行一段可執(zhí)行代碼的時候會創(chuàng)建一個執(zhí)行上下文橄仍,對于每一個執(zhí)行上下文都有三個重要屬性绣溜。
- 變量對象(Variable object,VO)
- 作用域鏈(Scope chain)
- this
在全局上下文中,它的變量對象就是全局對象伦仍。
函數(shù)上下文的變量對象也叫活動對象结窘。因為變量對象不可訪問,只有進入一個執(zhí)行上下文,變量對象別激活后,才能被訪問,所以我們叫他活動對象。
執(zhí)行上下文的執(zhí)行過程分為兩段
進入執(zhí)行上下文
在這個階段中充蓝,執(zhí)行上下文會分別創(chuàng)建變量對象隧枫,建立作用域鏈,以及確定this的指向谓苟。
執(zhí)行代碼
創(chuàng)建完成之后官脓,就會開始執(zhí)行代碼,這個時候涝焙,會完成變量賦值卑笨,函數(shù)引用,以及執(zhí)行其他代碼仑撞。
當(dāng)進入執(zhí)行上下文時赤兴,這時候還沒有執(zhí)行代碼,
變量對象會包括:
- 函數(shù)的所有形參 (如果是函數(shù)上下文)
- 由名稱和對應(yīng)值組成的一個變量對象的屬性被創(chuàng)建
- 沒有實參隧哮,屬性值設(shè)為 undefined
- 函數(shù)聲明
- 由名稱和對應(yīng)值(函數(shù)對象(function-object))組成一個變量對象的屬性被創(chuàng)建
- 如果變量對象已經(jīng)存在相同名稱的屬性桶良,則完全替換這個屬性
- 變量聲明
- 由名稱和對應(yīng)值(undefined)組成一個變量對象的屬性被創(chuàng)建;
- 如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同沮翔,則變量聲明不會干擾已經(jīng)存在的這類屬性
讓我們來看個例子
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);
在進入執(zhí)行上下文后陨帆,這時候的 AO 是:
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
在代碼執(zhí)行階段,會順序執(zhí)行代碼,根據(jù)代碼歧譬,修改變量對象的值
還是上面的例子岸浑,當(dāng)代碼執(zhí)行完后,這時候的 AO 是:
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: 3,
c: reference to function c(){},
d: reference to FunctionExpression "d"
}
我們再來看看arguments對象是什么,引用js高程里的話:
調(diào)用函數(shù)時瑰步,會為其創(chuàng)建一個Arguments對象矢洲,并自動初始化局部變量arguments,指代該Arguments對象缩焦。所有作為參數(shù)傳入的值都會成為Arguments對象的數(shù)組元素读虏。
當(dāng)查找變量的時候,會先從當(dāng)前上下文的變量對象中查找袁滥,如果沒有找到盖桥,就會從父級(詞法層面上的父級)執(zhí)行上下文的變量對象中查找,一直找到全局上下文的變量對象题翻,也就是全局對象揩徊。這樣由多個執(zhí)行上下文的變量對象構(gòu)成的鏈表就叫做作用域鏈。
我們來看例子嵌赠。
var scope = "global scope";
function checkscope() {
var scope2 = 'local scope';
return scope2;
}
checkscope();
//1.checkscope被創(chuàng)建
//保存父元素的變量對象到他的內(nèi)部屬性[[scope]]上
checkscope.[[scope]] = [globalContext.VO]
// 2.開始執(zhí)行checkscope函數(shù) 創(chuàng)建checkscope的執(zhí)行上下文,同時chec
ECStack = [
checkscopeContext,
globalContext
]
//3.checkscope函數(shù)還需要做一些準(zhǔn)備工作才能執(zhí)行
// 第一步,復(fù)制[[scope]]屬性創(chuàng)建作用域鏈
checkscopeContext = {
Scope: checkscope.[[scope]]
}
// 第二步,用arguments創(chuàng)建活動對象塑荒,并初始化
checkscopeContext = {
Ao: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]]
}
// 第三步,把自己的活動對象壓入Scope中
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, checkscope.[[scope]]]
}
// 4.終于準(zhǔn)備工作做完,可以開始執(zhí)行函數(shù),修改AO的屬性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
// 5.查到了scope2的值,返回后函數(shù) 執(zhí)行完畢,把函數(shù)上下文從執(zhí)行上下文中彈出
ECStack = [
globalContext
];
再來看一個
function a() {
var aaa = 123;
function b(){ //函數(shù)b的創(chuàng)建是在a的執(zhí)行上下文準(zhǔn)備階段創(chuàng)建的,這時候就有了a的AO,所以b創(chuàng)建的時候b.[[scope]] = [a.Ao] =
console.log(aaa);
aaa=234;
};
b();
console.dir(aaa);
};
a();
對于上面的例子
- function b有自己的作用域姜挺,內(nèi)部定義了aaa齿税,所以在它的VO中aaa是undefined
- function a的VO中也定義了aaa,值是123炊豪;
- function b執(zhí)行時會順著它的作用域鏈做變量查找(reference resolution)凌箕,先找 到自己定義的aaa,輸出是undefined词渤,因為自己的VO在作用域鏈的第一個位置牵舱,最先被查找。
- 自然缺虐,運行時修改的也是b自己AO中的aaa芜壁,所以不會影響到a中的aaa。