面向對象的 Javascript 閉包
面向對象的 Javascript 閉包 收藏
閉包
閉包意味着內層的函數可以引用存在於包繞它的函數的變量,即使外層的函數的執行已經終止。這一特殊的論題可能是非常強大又非常複雜的。我強烈推薦你們參考本節後面將提及的站點,因爲它有一些關於閉包這一話題的精彩的信息。
我們先來看程序2-13所示的閉包的兩個簡單例子。
程序2-13. 閉包改善的代碼清晰性的兩例
//得到id爲"main"的元素
var obj = document.getElementById("main");
//改變它的邊框樣式
obj.style.border = "1px solid red";
//初始化一個1秒鐘以後被調用的回調函數
setTimeout(function(){
//此函數將隱藏該元素
obj.style.display = 'none';
}, 1000);
//用來延遲顯示消息的通用函數
function delayedAlert( msg, time ) {
//初始化一個被封套的函數
setTimeout(function(){
//此函數使用了來自封套它的函數的變量msg
alert( msg );
}, time );
}
//調用函數delayedAlert,帶兩個參數
delayedAlert( "Welcome!", 2000 );
第一個對setTimeout的函數調用,展示了一個的JavaScript新手遇到問題的通俗的例子。在JavaScript新手的程序裏像這樣的代碼時常可以看到:
setTimeout("otherFunction()", 1000);
//或者甚至
setTimeout("otherFunction(" + num + "," + num2 + ")", 1000);
使用閉包的概念,完全可能的把這種混亂的代碼清理掉。第一個例子很簡單;有一個回調函數在調用setTimeout函數以後1000微秒以後被調用,而它仍引用了變量obj(定義在全局範圍,指向id爲"main"的元素)。定義的第二個函數,delayedAlert,展示了一種解決出現的 setTimeout混亂的方案,以及函數作用域內可以有閉包的能力。
你們應該可以發現,當在代碼中使用這種簡單的閉包時,你所寫的東西的清晰性將會提高,免於陷入語法的迷霧之中。
我們來看一個閉包可能帶來的有有趣的副作用。在某些函數化的編程語言裏,有一個叫做currying 的概念。本質上講,currying是就是爲函數的一些參數預填入值,創建一個更簡單的新函數的方法。代碼2-14裏有一個簡單的currying的例子,創建了向另一個函數預填一個參數而得的新函數。
代碼2-14. 使用閉包的函數currying
//生成做加法的新函數的函數
function addGenerator( num ) {
//返回一個簡單函數用來計算兩個數的加法,
//其中第一個數字從生成器中借用
return function( toAdd ) {
return num + toAdd
};
}
//addFive現在是接受一個參數的函數,
//此函數將給參數加5,返回結果數字
var addFive = addGenerator( 5 );
//這裏我們可以看到,當傳給它參數4的時候
//函數addFive的結果爲9
alert( addFive( 4 ) == 9 );
閉包還能解決另一個常見的JavaScript編碼方面的問題。JavaScript新手趨向於在全局作用域裏放置許多變量。這一般被認爲是不好的習慣,因爲那些變量可能悄悄地影響其它的庫,導致令人迷惑的問題的產生。使用一個自執行的、匿名的函數,你可以從根本上隱藏所有的通常的全局變量,使它們對其它代碼不可見,如程序2-15所示。
代碼2-15. 使用匿名函數從全局作用域隱藏變量的例子
//創建一個用作包裝的匿名函數
(function(){
//這個變量通常情況下應該是全局的
var msg = "Thanks for visiting!";
//爲全局對象綁定新的函數
window.onunload = function(){
//使用了“隱藏”的變量
alert( msg );
};
//關閉匿名函數並執行之
})();
最後,讓我們來看使用閉包時出現的一個問題。閉包允許你引用存在於父級函數中的變量。然而,它並不是提供該變量創建時的值;它提供的是父級函數中該變量最後的值。你會看到這個問題最通常是在一個for循環中。有一個變量被用作迭代器(比如i),在for內部新的函數被創建,並使用了閉包來引用該迭代器。問題是,當新的閉包函數被調用時,它們將會引用該iterator最後的值(比如,一個數組的最後位置),而不是你所期望的那個。程序2-16的例子說明,使用匿名函數激發作用域,在其中創建一個合乎期望的閉包是可能的。
程序2-16. 使用匿名函數激發一個創建多個閉包函數所需的作用域的例子
//id爲"main"的一個元素
var obj = document.getElementById("main");
//用來綁定的items數組
var items = [ "click", "keypress" ];
//遍歷items中的每一項
for ( var i = 0; i < items.length; i++ ) {
//用自執行的匿名函數來激發作用域
(function(){
//在些作用域內存儲值
var item = items[i];
//爲obj元素綁定函數
obj[ "on" + item ] = function() {
//item引用一個父級的變量,
//該變量在此for循環的上文中已被成功地scoped(?)
alert( "Thanks for your " + item );
};
})();
}
閉包的概念並非輕易可以掌握的;我着實花了大量的時間和精力才徹底弄清閉包有多麼強大。幸運的是,有一個精彩的資源解釋了JavaScript 中的閉包是怎麼工作的:Jim Jey的"JavaScript閉包",網址是http://jibbering.com/faq/faq_notes/closures.html。
最後,我們將研究上下文的概念,這是許多JavaScript的面向對象特性賴以建立的基石。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.