函數執行環境
簡單的代碼:
復制代碼 代碼如下:
function say(msg,other){
var str = "nobody say:";
this.name = '笨蛋的座右銘';
function method(){};//var method = function(){};
alert(str+msg);
}
say('hello world');
alert(name);//笨蛋的座右銘
當調用say方法時,第一步是創建其執行環境,在創建執行環境的過程中,會按照定義的先後順序完成一系列操作:
1.首先會創建一個'活動對象'(Activation Object)。活動對象是規范中規定的另外一種機制。之所以稱之為對象,是因為它擁有可訪問的命名屬性,但是它又不像正常對象那樣具有原型(至少沒有預定義的原型),而且不能通過 JavaScript 代碼直接引用活動對象。
2.為函數調用創建執行環境的下一步是創建一個 arguments 對象,這是一個類似數組的對象,它以整數索引的數組成員一一對應地保存著調用函數時所傳遞的參數。這個對象也有 length 和 callee 屬性(更深入的內容,請參見《理解Javascript_14_函數形式參數與arguments 》)。然後,會為活動對象創建一個名為“arguments”的屬性,該屬性引用前面創建的 arguments對象。
3.接著,為執行環境分配作用域。作用域由對象列表(鏈)組成。(比較復雜,請參見:《理解Javascript_15_作用域分配與變量訪問規則,再送個閉包》)
4.之後會發生由 ECMA 262 中所謂'活動對象'完成的'變量實例化'(Variable Instatiation)的過程。此時會將函數的形式參數創建為可變對象的命名屬性,如果調用函數時傳遞的參數與形式參數一致,則將相應參數的值賦給這些命名屬性(否則,會給命名屬性賦 undefined 值)。對於定義的內部函數,會以其聲明時所用名稱為可變對象創建同名屬性,而相應的內部函數則被創建為函數對象並指定給該屬性。變量實例化的最後一步是將在函數內部聲明的所有局部變量創建為可變對象的命名屬性。注:在這個過程中,除了實際參數有值外和函數定義外,其它都被'預解析'為undefined值.
5.最後,要為使用 this 關鍵字而賦值。(此時的this指向的是全局對象,即window)
執行環境創建成功後,就進入第二步:在函數體內,從上到下執行代碼。
1.當執行到var str='nobody say'會發生稱之為'計算賦值表達式'的過程,此時,會將活動對象中key為str的值從undefined設置為'nobody say:'。
2.執行到this.name='笨蛋的座右銘'時,會為作為this的對象添加屬性name,並賦值為'笨蛋的座右銘'.
3.然後是執行function innerMethod(){};最後執行'alert(str+msg),輸出'nobody say:hello world'.
function method(){}與var method = function(){}的區別
簡單的代碼:
復制代碼 代碼如下:
function say(){
method01();//method01
method02();//error
function method01(){
alert('method01');
}
var method02 = function(){
alert('method02');
}
}
say();
為什麼調用方法method01能正常運行,而調用方法method02卻會報錯呢? 首先,你要明確的知道method01為一個函數對象,而method02為一個變量,它指向於另一個函數對象。根據上一節的內容,在'活動對象'完成的'變量實例化'(Variable Instatiation)的過程中,對函數method01進行了正常的'預解析',而對於變量method02解析為undefined值,當進入到執行代碼的環節時,因為method02的調用在其計算函數表達式之前,因此將undefined當作方法調來用,必然會報錯。解決方案比較簡單,就是將var method02=function(){...}放到其method02()的調用之前就可以了或者是用函數聲明的方式定義函數(function method(){...})。
注:計算函數表達式就是指程序執行到var method02 = function(){...}。method02此時真正指向一個函數對象。因為在'預解析'時,method02只被賦於了undefined.
全局執行環境(《執行模型淺析》中已經講過,不再深入)
在一個頁面中,第一次載入JS代碼時創建一個全局執行環境,全局執行環境的作用域鏈實際上只由一個對象,即全局對象(window),在開始JavaScript代碼的執行之前,引擎會創建好這個Scope Chain結構。全局執行環境也會有變量實例化的過程,它的內部函數就是涉及大部分 JavaScript 代碼的、常規的頂級函數聲明。而且,在變量實例化過程中全局對象就是可變對象,這就是為什麼全局性聲明的函數是全局對象屬性的原因。全局性聲明的變量同樣如此全局執行環境會使用 this 對象來引用全局對象。
注:區別'函數執行環境'中的活動對象(Activation Object)和全局執行環境中的可變對象(Vriable Object),Variable Object也叫做Activation Object(因為有一些差異存在,所以規范中重新取一個名字以示區別,全局執行環境/Eval執行環境中叫Variable Object,函數執行環境中就叫做Activation Object)。
Eval執行環境
構建Eval執行環境時的可變對象(Variable Object)就是調用eval時當前執行上下文中的可變對象(Variable Object)。在全局執行環境中調用eval函數,它的可變對象(Variable Object)就是全局對象;在函數中調用eval,它的可變對象(Variable Object)就是函數的活動對象(Activation Object)。
復制代碼 代碼如下:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
function fn(arg){
var innerVar = "variable in function";
eval(' \
var evalVar = "variable in eval"; \
document.write(arg + "<br />"); \
document.write(innerVar + "<br />"); \
');
document.write(evalVar);
}
fn("arguments for function");
輸出結果是:
arguments for function
variable in function
variable in eval
說明: eval調用中可以訪問函數fn的參數、局部變量;在eval中定義的局部變量在函數fn中也可以訪問,因為它們的Varible Object是同一個對象。
進入Eval Code執行時會創建一個新的Scope Chain,內容與當前執行上下文的Scope Chain完全一樣。
最後的實例
代碼如下:
復制代碼 代碼如下:
var outerVar1="variable in global code";
function fn1(arg1, arg2){
var innerVar1="variable in function code";
function fn2() { return outerVar1+" - "+innerVar1+" - "+" - "+(arg1 + arg2); }
return fn2();
}
var outerVar2=fn1(10, 20);
執行處理過程大致如下:
1. 初始化Global Object即window對象,Variable Object為window對象本身。創建Scope Chain對象,假設為scope_1,其中只包含window對象。
2. 掃描JS源代碼(讀入源代碼、可能有詞法語法分析過程),從結果中可以得到定義的變量名、函數對象。按照掃描順序:
2.1 發現變量outerVar1,在window對象上添加outerVar1屬性,值為undefined;
2.2 發現函數fn1的定義,使用這個定義創建函數對象,傳給創建過程的Scope Chain為scope_1。將結果添加到window的屬性中,名字為fn1,值為返回的函數對象。注意fn1的內部[[Scope]]就是 scope_1。另外注意,創建過程並不會對函數體中的JS代碼做特殊處理,可以理解為只是將函數體JS代碼的掃描結果保存在函數對象的內部屬性上,在函數執行時再做進一步處理。這對理解Function Code,尤其是嵌套函數定義中的Variable Instantiation很關鍵;
2.3 發現變量outerVar2,在window對象上添加outerVar2屬性,值為undefined;
3. 執行outerVar1賦值語句,賦值為"variable in global code"。
4. 執行函數fn1,得到返回值:
4.1 創建Activation Object,假設為activation_1;創建一個新的Scope Chain,假設為scope_2,scope_2中第一個對象為activation_1,第二個對象為window對象(取自fn1的 [[Scope]],即scope_1中的內容);
4.2 處理參數列表。在activation_1上設置屬性arg1、arg2,值分別為10、20。創建arguments對象並進行設置,將arguments設置為activation_1的屬性;
4.3 對fn1的函數體執行類似步驟2的處理過程:
4.3.1 發現變量innerVar1,在activation_1對象上添加innerVar1屬性,值為undefine;
4.3.2 發現函數fn2的定義,使用這個定義創建函數對象,傳給創建過程的Scope Chain為scope_2(函數fn1的Scope Chain為當前執行上下文的內容)。將結果添加到activation_1的屬性中,名字為fn2,值為返回的函數對象。注意fn2的內部 [[Scope]]就是scope_2;
4.4 執行innerVar1賦值語句,賦值為"variable in function code"。
4.5 執行fn2:
4.5.1 創建Activation Object,假設為activation_2;創建一個新的Scope Chain,假設為scope_3,scope_3中第一個對象為activation_2,接下來的對象依次為activation_1、window 對象(取自fn2的[[Scope]],即scope_2);
4.5.2 處理參數列表。因為fn2沒有參數,所以只用創建arguments對象並設置為activation_2的屬性。
4.5.3 對fn2的函數體執行類似步驟2的處理過程,沒有發現變量定義和函數聲明。
4.5.4 執行函數體。對任何一個變量引用,從scope_3上進行搜索,這個示例中,outerVar1將在window上找到;innerVar1、arg1、arg2將在activation_1上找到。
4.5.5 丟棄scope_3、activation_2(指它們可以被垃圾回收了)。
4.5.6 返回fn2的返回值。
4.6 丟棄activation_1、scope_2。
4.7 返回結果。
5. 將結果賦值給outerVar2。
參考:
http://www.cnblogs.com/RicCC/archive/2008/02/15/JavaScript-Object-Model-Execution-Model.html
http://www.cn-cuckoo.com/2007/08/01/understand-javascript-closures-72.html