欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

javascript 必知必會(huì)之closure

 更新時(shí)間:2009年09月21日 19:55:58   作者:  
本系列博文主要談一些在 javascript 使用中經(jīng)常會(huì)混淆的高級(jí)應(yīng)用,包括: prototype, closure, scope, this關(guān)鍵字. 對(duì)于一個(gè)需要提高自己javascript水平的程序員,這些都是必須要掌握的.
下面的代碼片斷縮進(jìn)目前還不完善,你也可以選擇 下載pdf 來(lái)閱讀.

什么是closure

一種定義是:

A "closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).

我的理解是: closure 是一個(gè)表達(dá)式(通常是一個(gè)函數(shù)), 這個(gè)表達(dá)式與一個(gè) 環(huán)境 共享著一些自由變量, 而這個(gè) 環(huán)境 則 綁定 著那些自由變量(或者說(shuō) 結(jié)束 這個(gè)表達(dá)式, 這也是所謂closure 的名字由來(lái)). 所謂的 環(huán)境 就是一個(gè)更大的block, 所有的自由變量在這個(gè) block 中 聲明(有意義). 而 綁定 也就是指這些自由變量的作用域就是這個(gè)環(huán)境.

舉個(gè)簡(jiǎn)單的例子.

var flag = false; //調(diào)試開關(guān) 
// env 既是所謂的環(huán)境 
// 而inner就是所謂的表達(dá)式, name即是所謂的自由變量 
function env() //整個(gè)env可以看作是一個(gè)closure 
{ 
var name = "zhutao"; 
function inner() 
{ 
return name + " is a student."; 
} 
return inner; //返回的是一個(gè)內(nèi)部函數(shù) 
}//closure結(jié)束 
flag = true; 
if (flag) 
{ 
// 此處是最神奇的地方, 代碼執(zhí)行在此處, inner函數(shù)其實(shí)已經(jīng)出了env的body, 
// 而仍然能夠被引用, 這就是所謂形成了一個(gè) closure 
var inner_func_ref = env(); // 這時(shí)候inner_func_ref引用的就是inner()函數(shù)對(duì)象 
alert(inner_func_ref()); // zhutao is a student. 
} 

而在上面的例子中, 函數(shù)env就是所謂的定義中的 環(huán)境, 函數(shù)inner就是定義中所謂的 表達(dá)式, 而name即是所謂的 自由變量綁定 在env這個(gè) 環(huán)境 中. env的結(jié)束也即closure的結(jié)束.

而在javascript中,如果內(nèi)部函數(shù)出了自己的所在的外部函數(shù)的body仍然能夠引用,則會(huì)形成所謂的closure.

在具體了解closure之前,我們需要了解一些其它的知識(shí).

執(zhí)行空間(執(zhí)行上下文, Execution Context)

在 javascript 中,每行可執(zhí)行的代碼都具有一定的 執(zhí)行空間, 如全局的執(zhí)行空間, 函數(shù)的執(zhí)行空間, 遞歸后的函數(shù)執(zhí)行空間等. 而一個(gè)完整的 javascript 執(zhí)行過(guò)程,可以看作是有一個(gè)執(zhí)行空間棧 ,不斷地 進(jìn)行 執(zhí)行空間 的變化(出棧,進(jìn)棧).

這個(gè)是很重要的概念,這個(gè)概念的理解與本系列的將要完成的另一篇文章 this關(guān)鍵字 的理解也是密切相關(guān)的.

詳細(xì)解釋請(qǐng)參考即將完成的 this關(guān)鍵字 的博文.

執(zhí)行空間可以理解為具有屬性的對(duì)象集, 但是通常這些屬性都不是可隨意訪問(wèn)的, 而這些對(duì)象集為代碼的執(zhí)行 提供了一定的上下文(空間).

當(dāng)執(zhí)行到一個(gè)函數(shù)時(shí), 會(huì)建立此函數(shù)的執(zhí)行空間(所謂進(jìn)棧), 執(zhí)行結(jié)束了, 從此執(zhí)行空間退出返回到原來(lái)的執(zhí)行空間(所謂 的出棧),而js解釋器在運(yùn)行過(guò)程中一起維護(hù)著這樣一個(gè) 執(zhí)行空間棧 來(lái)為不同的代碼提供不同的執(zhí)行空間.

那么執(zhí)行空間與closure有什么關(guān)系?

簡(jiǎn)單地說(shuō),一定的執(zhí)行空間對(duì)應(yīng)著一定的closure, 只有位于同一個(gè)closure的方法才能訪問(wèn)同一closure的變量.

舉個(gè)簡(jiǎn)單的例子:

// 關(guān)于context的例子 
flag = true; 
var tmpobj = { 
name : "zhutao", 
func : function(){ 
return "call by func " + this.name; 
} 
}; 
if (flag) 
{ 
// 代碼執(zhí)行在此處時(shí)context還是global 
alert(tmpobj.name); 
alert(tmpobj.func()); //進(jìn)入func的context 
// 回到global的context 
} 

closure的一些用法

當(dāng)內(nèi)部函數(shù)和自由變量位于同一closure時(shí),可以隨意訪問(wèn),而聲明順序并不重要.

幾個(gè)常用的例子:

//一些應(yīng)用 
flag = true; 
function OuterFun() 
{ 
var num = 100; 
var printNum = function(){alert(num);} //此處引用的num是引用,而不是值,所以后面改變num,此處的num同樣生效 
num ++; 
return printNum; 
} 
var myfunc = OuterFun(); 
myfunc(); //輸出的是101,而不是100 
//另一個(gè)例子,下面的例子,可以看到匿名函數(shù)(內(nèi)部函數(shù))先于外部函數(shù)變量的聲明,但是仍然能夠訪問(wèn)外部函數(shù)的變量 
// 也就是說(shuō)內(nèi)部函數(shù)與外部函數(shù)的變量位于同一個(gè)closure, 所以可以訪問(wèn) 
function SameClosure() 
{ 
var iCanAccess = function(){alert(name);}; 
var name = "zhutao"; 
return iCanAccess; 
} 
var testSameClosure = SameClosure(); 
testSameClosure();// zhutao 
// 另一個(gè)應(yīng)用,關(guān)于module pattern, 這樣可以實(shí)際所謂的 private, public等方法和變量 
var module = (function Module(){ 
var privateVar = "zhutao is private"; // private 
return { 
publicGetPrivateVar : function(){ 
return privateVar; 
}, // public method, 可以取所謂的private變量 
publicVar : "I'm a public variable" // public variable 
}; 
})(); 
if (flag) 
{ 
alert(module.publicGetPrivateVar()); // zhutao is private 
alert(module.publicVar); // I'm a public variable 
alert(module.privateVar); // undefined 
} 

關(guān)于closure的效率

因?yàn)樵赾losure的實(shí)際應(yīng)用可能會(huì)多次去生成一個(gè)內(nèi)部函數(shù)(匿名),所以存在可能的效率問(wèn)題.(對(duì)象的建立,內(nèi)存管理釋放等).

所以,應(yīng)該盡量減少內(nèi)部函數(shù)的生成, 而使用函數(shù)的引用.

例如:

// 關(guān)于效率的例子 
flag = false; 
// 這樣,每次調(diào)用Outer時(shí)會(huì)產(chǎn)生匿名函數(shù)的開銷 
function Outer(obj) 
{ 
obj.fun = function(){ 
alert("I am " + this.name); 
}; 
} 
if (flag) 
{ 
var obj = { name : "zhutao"}; 
Outer(obj); 
obj.fun(); 
} 
// 更好的處理方式 
function Outer_better(obj) 
{ 
obj.fun = showme; // 這樣調(diào)用的只是函數(shù)的引用 
} 
function showme() 
{ 
alert("I am " + this.name); 
} 
if (flag) 
{ 
var obj2 = { name : "zhutao"}; 
Outer_better(obj2); 
obj2.fun(); 
} 

應(yīng)用建議

Don't use closures unless you 
really need closure semantics. 
In most cases, nonnested 
functions are the right way to go. 
Eric Lippert, Microsoft 

上面的論述是基于效率的考慮, 而 IE 4-6 在使用closure時(shí)可能會(huì)存在內(nèi)存泄露的問(wèn)題,參考 JavaScript Closures 中的相關(guān)部分.

而在某些場(chǎng)合,你可能必須要使用closure, 如 循環(huán)問(wèn)題.

代碼:

flag = true; 
// 向body中生成一些鏈接,然后綁定事件 
function addLink(num) 
{ 
for(var i=0; i<num; i++) 
{ 
var link = document.createElement('a'); 
link.innerHTML = "Link " + i; 
link.onclick = function(){ 
alert(i); 
}; 
document.body.appendChild(link); 
} 
} //可惜的是,當(dāng)你點(diǎn)擊每個(gè)鏈接時(shí),輸出的都是 Link 4 
// 使用closure 可以解決這個(gè)問(wèn)題 
function addLink2(num) 
{ 
for(var i=0; i<num; i++) 
{ 
var link = document.createElement('a'); 
link.innerHTML = "Link" + i; 
link.onclick = function(j){ //使用closure 
return function(){ 
alert(j); 
};//返回一個(gè)函數(shù) 
}(i);//調(diào)用這個(gè)函數(shù) 
document.body.appendChild(link); 
} 
} 
window.onload = addLink(4); 
window.onload = addLink2(4); 

為什么會(huì)出現(xiàn)上面的這個(gè)問(wèn)題?(事實(shí)在之前的的一個(gè)項(xiàng)目中,也遇到了相同的問(wèn)題,但是當(dāng)時(shí)還不懂closure, 也是一頭霧水)

這是因?yàn)?對(duì)于addLink, 在退出addLink函數(shù)之前, i已經(jīng)變成了4,所以無(wú)論后面的事件觸發(fā),輸出的都是4.

但是后者,使用了closure.使得j引用了當(dāng)前的循環(huán)中的i,所以對(duì)于每個(gè)后續(xù)觸發(fā)事件,都會(huì)按照預(yù)期地得到相應(yīng)的結(jié)果.

具體的討論可見: SO

這即是一個(gè)典型的closure應(yīng)用場(chǎng)景, 而如果不使用, 就無(wú)法解決這個(gè)問(wèn)題.

結(jié)論

下面這段摘抄自 Summary of JavaScript closures :

  1. 當(dāng)你在一個(gè)函數(shù)中使用另一個(gè)函數(shù)時(shí), 會(huì)產(chǎn)生一個(gè)closure
  2. 當(dāng)你使用eval()時(shí), 會(huì)產(chǎn)生一個(gè)closure.
  3. 最好認(rèn)為closure總是在函數(shù)入口處產(chǎn)生,并且本地變量自動(dòng)添加到closure中

其它的細(xì)節(jié)可參考上面的鏈接.

總之, 關(guān)于closure,你必須記住以下幾點(diǎn):

  1. closure就是提供了一種變量共享的機(jī)制(內(nèi)部函數(shù)可以訪問(wèn)外部函數(shù)的變量)
  2. 注意closure可能引用的效率問(wèn)題(如何避免,參見文中詳述)
  3. 具體的應(yīng)用場(chǎng)景要熟悉

上篇博文講的是 prototype, 下篇博文預(yù)計(jì)會(huì)講 this關(guān)鍵字, 歡迎大家討論和留言.

本文的rst源碼

本文的源碼鏈接在 這里 .

本文中涉及的javascript代碼可以在 這兒 下載.

你也可以選擇 下載pdf 來(lái)閱讀.

相關(guān)文章

  • 腳本安需導(dǎo)入(裝載)的三種模式的對(duì)比

    腳本安需導(dǎo)入(裝載)的三種模式的對(duì)比

    腳本安需導(dǎo)入(裝載)的三種模式的對(duì)比...
    2007-06-06
  • 最新評(píng)論