(一)作用域:
首先,在javascript中的每個函數都是對象,是Funtion對象的一個實例,而Funtion中有一系列僅供javascript引擎存取的內部屬性,其中一個便是[[scope]],它包含了一個函數被創建的作用域中對象的集合,這個集合就是函數的作用域鏈。當一個函數創建后,它的作用域鏈會被創建此函數的作用域中可訪問的數據對象填充。例如定義下面這樣一個函數:
function add(num1,num2){
var sum = num1+num2;
return sum;
}
在函數add創建時,它的作用域鏈中會填入一個單獨的可變對象,即全局對象,該全局對象包含了所有全局變量,如下圖所示(注意:圖片只例舉了全部變量中的一部分):
函數add的作用域將在執行時候用到,假設var total = add(5,10);執行此函數會創建一個稱為執行上下文的內部對象,一個執行上下文定義了一個函數執行時的環境,函數每次執行時對應的執行上下文都是獨一無二的,所以多次調用統一個函數會導致創建多個執行上下文,當函數執行完畢,執行上下文被銷毀。每個執行下文都有自己的作用域鏈,用於標識符解析,當執行上下文被創建時,它的作用域鏈初始化為當前運行函數的[[Scope]]所包含的對象。這些值按照它們出現在函數中的順序被復制到運行期上下文的作用域鏈中。它們共同組成了一個新的對象,叫“活動對象(activation object)”,活動對象作為函數運行期的可變對象,該對象包含了函數的所有局部變量、命名參數、參數集合以及this,即它通過函數的arguments屬性初始化,arguments屬性的值是Arguments對象:
【Arguments對象是活動對象的一個屬性,它包括如下屬性:
- callee — 指向當前函數的引用
- length — 真正傳遞的參數個數
- properties-indexes (字符串類型的整數) 屬性的值就是函數的參數值(按參數列表從左到右排列)。 properties-indexes內部元素的個數等於arguments.length. properties-indexes 的值和實際傳遞進來的參數之間是共享的。
這個共享其實不是真正的共享一個內存地址,而是2個不同的內存地址,使用JavaScript引擎來保證2個值是隨時一樣的,當然這也有一個前提,那就是 這個索引值要小於你傳入的參數個數,也就是說如果你只傳入2個參數,而還繼續使用arguments[2]賦值的話,就會不一致,如:
function b(x, y, a) { arguments[2] = 10; alert(a); } b(1, 2);
這時候因為沒傳遞第三個參數a,所以賦值10以后,alert(a)的結果依然是undefined,而不是10,但如下代碼彈出的結果依然是10,因為和a沒有關系。
function b(x, y, a) { arguments[2] = 10; alert(arguments[2]); } b(1, 2);
】 然后此對象會被推入作用域鏈的前端,當運行期上下文被銷毀,活動對象也隨之銷毀。新的作用域鏈如下圖所示:
在 函數執行過程中,每遇到一個變量,都會經歷一次標識符解析過程以決定從哪里獲取和存儲數據。該過程從作用域鏈頭部,也就是從活動對象開始搜索,查找同名的 標識符,如果找到了就使用這個標識符對應的變量,如果沒找到繼續搜索作用域鏈中的下一個對象,如果搜索完所有對象都未找到,則認為該標識符未定義。函數執 行過程中,每個標識符都要經歷這樣的搜索過程。注意:如果名字相同的兩個變量存儲在作用域鏈的不同部分,那么標識符就是遍歷作用域時最先找到的那一個。
(二):閉包
閉包,簡單點說就是函數里面嵌套函數如:
function a(){
var name,age;
function b(){
console.log(name);
console.log(age);
} }
例子中的b函數就是一層閉包,閉包的強大之處在於它允許函數訪問作用域之外的數據,如例子中在b函數內部訪問a函數中的變量,在看一個例子:
funtion assignEvents(){
var id = "xdi9592";
document.getElementById("save-btn").onclick = function(event){
saveDocument(id);
}}
assignEvents函數中的事件處理器就是一個閉包,他在assignEvents執行時被創建,並且能訪問所屬作用域的id變量。當assignEvents()被執行時,一個包含了變量id和其他一些數據的活動對象被創建,即為運行期上下文作用域中的一個對象,當閉包被創建時,它的[[scope]]屬性被初始化為這些對象,如圖:
因為閉包的[[scope]]屬性包含了與運行期上下文作用域鏈相同的對象的引用,因此,當外層函數執行完畢時,由於引用仍然存在閉包的[[scope]]屬性中,故活動對象無法被銷毀,會使內存消耗大,即造成內存泄漏。
當閉包被執行的時候,一個執行上下文又被創建,它的作用域鏈與屬性[[scope]]中引用的兩個相同的作用域鏈對象同時被初始化,然后一個活動對象被閉包自身所創建,如圖: