淺談JavaScript閉包

什麼是閉包

JavaScript 函數是將要執行的代碼及執行這些代碼的作用域構成的一個綜合體。計算機術語稱這種代碼作用域的綜合體為閉包。故所有JavaScript 函數都是閉包。
但我們常說的JavaScript閉包是指,一個嵌套函數被導出到它所定義的作用域外時,才明確地稱為閉包

JavaScript閉包

閉包是 JavaScript 一個非常重要的特性,這意味著當前作用域總是能夠訪問外部作用域中的變量。 因為函數JavaScript 中唯一擁有自身作用域的結構,因此閉包的創建依賴於函數。


簡單寫法

該函數的私有持久變量,可以被多個函數共享

var uniqueID = (function() {
    // 私有持久值
    var id = 0;
    return function() {
        return id++;
    }
})();

循環中的閉包

一個常見的錯誤出現在循環中使用閉包,開發人員在循環語句裡創建函數(內部計數)時經常得不到預期的結果,假設我們需要在每次循環中調用循環序號

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);  
    }, 1000);
}

所輸入的內容不是 0-9,取而代之的是打印10次 10

關鍵原因,在調用console.log(i)時,循環已經結束,同一個上下文中創建的閉包是共用一個[[Scope]]屬性,導致i已經被修改成瞭10

在ECMAScript中,同一個父上下文中創建的閉包是共用一個[[Scope]]屬性的。也就是說,某個閉包對其中[[Scope]]的變量做修改會影響到其他閉包對其變量的讀取。

解決方式

避免引用錯誤,獲取正確序號。我們需要引入自執行函數,包裹一下。傳入就是i的拷貝,這樣就能獲取正確的輸出。

for(var i = 0; i < 10; i++) {
    (function(e) {
        setTimeout(function() {
            console.log(e);  
        }, 1000);
    })(i);
}

setTimeout包裹在一個匿名函數中,匿名函數擁有變量e的引用,便不用被循環改變瞭。

使用閉包的斷點

在此貼上《JavaScript權威指南》中,使用閉包的斷點代碼。由Steve
Yen
所寫,用來捕獲一個函數中的當前作用域(包括局部變量和函數的參數),並返回其結果。

function inspect(inspector, title) {
	var expression, result;
	if("ignore" in arguments.callee) {
		return;
	}

	while(true) {
		var message = "";
		if(title) {
			message = title + "\n";
		}

		if(expression) {
			message += "\n" + expression + " ==> " + result + "\n";
		} else {
			expression = "";
		}
		
		expression += "Enter an expression to evaluate:";
		expression = prompt(message, expression);

		if(!expression) {
			return;
		}

		result = inspector(expression);
	}
}

用斷點技術計算階乘的函數

function factorial(n) {
	var inspector = function($) {
		return eval($);
	}
	
	// inspect.ignore = true;
	inspect(inspector, "Entering factorial()");

	var result = 1;
	while(n > 1) {
		result *= n;
		n--;
		inspect(inspector, "factorial() loop");
	}

	inspect(inspector, "Exiting factorial()");
	return result;
}


參考

    JavaScript權威指南Closure

發佈留言