1. 執行上下文
1.1 什麼是執行上下文
var foo = function () {
console.log('foo1');
}
foo(); // foo1
var foo = function () {
console.log('foo2');
}
foo(); // foo2
// 變量提升
function foo() {
console.log('foo1');
}
foo(); // foo2
function foo() {
console.log('foo2');
}
foo(); // foo2
js的可執行代碼主要分爲全局代碼、函數代碼、eval代碼。當執行到某一類型的時間就會進入當前執行函數的執行上下文。
執行上下文棧是對多個執行上下文進行管理的一種棧
2 變量對象和活動對象
2.1 變量對象
變量對象是與執行上下文相關的數據作用域,存儲了在上下文中定義的變量和函數聲明。
2.2 活動對象
活動對象和變量對象其實是一個東西,只是變量對象是規範上的或者說是引擎實現上的,不可在 JavaScript 環境中訪問,只有當進入一個執行上下文中,這個執行上下文的變量對象纔會被激活,而只有被激活的變量對象,也就是活動對象上的各種屬性才能被訪問。
2.2 變量對象變爲活動對象的過程
- 函數上下文的變量對象初始化只包括 Arguments 對象
- 在進入執行上下文時會給變量對象添加形參、函數聲明、變量聲明等初始的屬性值,變量對象變爲活動對象
- 在代碼執行階段,會再次修改變量對象的屬性值
3 作用域和作用域鏈
3.1 作用域
程序定義的一套良好的規則來規範來對變量的存儲和訪問,這就是作用域。js主要有全局作用域和局部作用域,全局作用域,即對任何內部函數來說,都是可以訪問的;而局部作用域,一般只在固定的代碼片段內可訪問到。
3.2 作用域鏈
當查找變量的時候,會先從當前上下文的變量對象中查找,如果沒有找到,就會從父級(詞法層面上的父級)執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個執行上下文的變量對象構成的鏈表就叫做作用域鏈。
3.3 作用域鏈是如何創建和變化的
- 函數的作用域在函數定義的時候就決定了,在函數的[[scope]]
- 當函數激活時,進入函數上下文,就會將活動對象添加到作用鏈的前端。
用一個例子總結一下函數執行上下文中作用域鏈和變量對象的創建過程:
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
- checkscope 函數被創建,保存作用域鏈到內部屬性[[scope]]
checkscope.[[scope]] = [
globalContext.VO
];
- 執行 checkscope 函數,創建 checkscope 函數執行上下文,checkscope 函數執行上下文被壓入執行上下文棧
ECStack = [
checkscopeContext,
globalContext
];
- checkscope 函數並不立刻執行,開始做準備工作,第一步:複製函數[[scope]]屬性創建作用域鏈.
checkscopeContext = {
Scope: checkscope.[[scope]],
}
- 第二步:用 arguments 創建活動對象,隨後初始化活動對象,加入形參、函數聲明、變量聲明
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
}
- 第三步:將活動對象壓入 checkscope 作用域鏈頂端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
- 準備工作做完,開始執行函數,隨着函數的執行,修改 AO 的屬性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
- 查找到 scope2 的值,返回後函數執行完畢,函數上下文從執行上下文棧中彈出
ECStack = [
globalContext
];