1、變量對象(variable object)
原文:Every execution context has associated with it a variable object. Variables and functions declared in the source text are added as properties of the variable object. For function code, parameters are added as properties of the variable object.
簡言之就是:每一個執行上下文都會分配一個變量對象(variable object),變量對象的屬性由 變量(variable) 和 函數聲明(function declaration) 構成。在函數上下文情況下,參數列表(parameter list)也會被加入到變量對象(variable object)中作為屬性。變量對象與當前作用域息息相關。不同作用域的變量對象互不相同,它保存了當前作用域的所有函數和變量。
這里有一點特殊就是只有 函數聲明(function declaration) 會被加入到變量對象中,而 **函數表達式(function expression)**則不會。看代碼:
// 函數聲明 function a(){} console.log(typeof a); // "function" // 函數表達式 var a = function _a(){}; console.log(typeof a); // "function" console.log(typeof _a); // "undefined"
函數聲明的方式下,a會被加入到變量對象中,故當前作用域能打印出 a。
函數表達式情況下,a作為變量會加入到變量對象中,_a作為函數表達式則不會加入,故 a 在當前作用域能被正確找到,_a則不會。
2、活動對象(activation object)
原文:When control enters an execution context for function code, an object called the activation object is created and associated with the execution context. The activation object is initialised with a property with name arguments and attributes { DontDelete }. The initial value of this property is the arguments object described below.
The activation object is then used as the variable object for the purposes of variable instantiation.
簡言之:當函數被激活,那么一個活動對象(activation object)就會被創建並且分配給執行上下文。活動對象由特殊對象 arguments 初始化而成。隨后,他被當做變量對象(variable object)用於變量初始化。
用代碼來說明就是:
function a(name, age){ var gender = "male"; function b(){} } a(“k”,10);
a被調用時,在a的執行上下文會創建一個活動對象AO,並且被初始化為 AO = [arguments]。隨后AO又被當做變量對象(variable object)VO進行變量初始化,此時 VO = [arguments].concat([name,age,gender,b])。
作用域是指程序源代碼中定義變量的區域。
作用域規定了如何查找變量,也就是確定當前執行代碼對變量的訪問權限。
JavaScript 采用詞法作用域(lexical scoping),也就是靜態作用域。
(1)靜態作用域與動態作用域
因為 JavaScript 采用的是詞法作用域,函數的作用域在函數定義的時候就決定了。
而與詞法作用域相對的是動態作用域,函數的作用域是在函數調用的時候才決定的。
讓我們認真看個例子就能明白之間的區別:
var value = 1; function foo() { console.log(value); } function bar() { var value = 2; foo(); } bar(); // 結果是 ???
假設JavaScript采用靜態作用域,讓我們分析下執行過程:
執行 foo 函數,先從 foo 函數內部查找是否有局部變量 value,如果沒有,就根據書寫的位置,查找上面一層的代碼,也就是 value 等於 1,所以結果會打印 1。
而引用《JavaScript權威指南》的回答就是:
JavaScript 函數的執行用到了作用域鏈,這個作用域鏈是在函數定義的時候創建的。嵌套的函數 f() 定義在這個作用域鏈里,其中的變量 scope 一定是局部變量,不管何時何地執行函數 f(),這種綁定在執行 f() 時依然有效。
4、執行環境和作用域鏈(execution context and scope chain)
-
execution context
顧名思義 執行環境/執行上下文。在javascript中,執行環境可以抽象的理解為一個object,它由以下幾個屬性構成:executionContext:{ variable object:vars,functions,arguments, scope chain: variable object + all parents scopes thisValue: context object }
此外在js解釋器運行階段還會維護一個環境棧,當執行流進入一個函數時,函數的環境就會被壓入環境棧,當函數執行完后會將其環境彈出,並將控制權返回前一個執行環境。環境棧的頂端始終是當前正在執行的環境。 !通俗來講,就是: 當執行一個函數的時候,就會創建一個執行上下文,並且壓入執行上下文棧,當函數執行完畢的時候,就會將函數的執行上下文從棧中彈出。
-
執行上下文的代碼會分成兩個階段進行處理:分析和執行,我們也可以叫做:
- 進入執行上下文(當進入執行上下文時,這時候還沒有執行代碼)
- 代碼執行 (在代碼執行階段,會順序執行代碼,根據代碼,修改變量對象的值)
-
-
scope chain
作用域鏈,它在解釋器進入到一個執行環境時初始化完成並將其分配給當前執行環境。每個執行環境的作用域鏈由當前環境的變量對象及父級環境的作用域鏈構成。
作用域鏈具體是如何構建起來的呢,先上代碼:function test(num){ var a = "2"; return a+num; } test(1);
- 執行流開始 初始化function test,test函數會維護一個私有屬性 [[scope]],並使用當前環境的作用域鏈初始化,在這里就是 test.[[Scope]]=global scope.
- test函數執行,這時候會為test函數創建一個執行環境,然后通過復制函數的[[Scope]]屬性構建起test函數的作用域鏈。此時 test.scopeChain = [test.[[Scope]]]
- test函數的活動對象被初始化,隨后活動對象被當做變量對象用於初始化。即 test.variableObject = test.activationObject.contact[num,a] = [arguments].contact[num,a]
- test函數的變量對象被壓入其作用域鏈,此時 test.scopeChain = [ test.variableObject, test.[[scope]]];
至此test的作用域鏈構建完成。