閉包的理解
因?yàn)閮?nèi)部函數(shù)在被創(chuàng)建時(shí),其作用域鏈對(duì)外部函數(shù)對(duì)應(yīng)的變量對(duì)象存在一個(gè)引用,而JS采用引用計(jì)數(shù)的方法進(jìn)行內(nèi)存管理,所以當(dāng)外部函數(shù)被執(zhí)行完畢后,其對(duì)應(yīng)的變量對(duì)象不會(huì)被回收,這樣就發(fā)生了閉包,在外部函數(shù)執(zhí)行完畢后,我們?cè)趦?nèi)部函數(shù)中仍然可以訪問外部函數(shù)作用域中的變量。
閉包就是函數(shù)的局部變量集合,只是這些局部變量在函數(shù)返回后會(huì)繼續(xù)存在。
閉包就是就是函數(shù)的“堆?!痹诤瘮?shù)返回后并不釋放,我們也可以理解為這些函數(shù)堆棧并不在棧上分配而是在堆上分配,當(dāng)在一個(gè)函數(shù)內(nèi)定義另外一個(gè)函數(shù)就會(huì)產(chǎn)生閉包而當(dāng)一個(gè)函數(shù)中的變量或者函數(shù)有權(quán)訪問另一個(gè)函數(shù)作用域中的變量或者函數(shù)時(shí)就產(chǎn)生了閉包了
嵌套的函數(shù)定義
我們只能通過變通的辦法來(lái)訪問函數(shù)的局部變量,一般來(lái)說,這個(gè)變通的辦法就是在函數(shù)內(nèi)部再定義一個(gè)函數(shù),因?yàn)橐粋€(gè)函數(shù)不僅可以訪問全局變量,還可以訪問它的外部函數(shù)(Outer function)定義的局部變量,比如下面的代碼:
var global_var = "I'm global";
function outer() {
var local_var = "I'm local";
return function inner() {
console.log("local: " + local_var);
};
}
outer()(); // 輸出:local: I'm local
函數(shù) inner 不僅有自己的內(nèi)部作用域,還可以訪問全局變量,也可以訪問它外部的 outer 函數(shù)定義的所有局部變量。我們知道函數(shù)是 JavaScript 的一等公民,可以作為其他函數(shù)的參數(shù)或者作為函數(shù)的返回值,這里我們把 inner 函數(shù)返回,這樣我們就通過變通的方法在 outer 函數(shù)外部訪問了 outer 函數(shù)內(nèi)部定義的局部變量。那這里的內(nèi)部函數(shù) inner 就構(gòu)成了閉包。在 JavaScript 語(yǔ)言中,閉包的定義可以簡(jiǎn)化為嵌套定義在函數(shù)內(nèi)部的函數(shù)。
instanceof和typeof都能用來(lái)判斷一個(gè)變量是否為空或是什么類型的變量。typeof用以獲取一個(gè)變量的類型,typeof一般只能返回如下幾個(gè)結(jié)果:number,boolean,string,function,object,undefined。我們可以使用typeof來(lái)獲取一個(gè)變量是否存在,如if(typeof a!="undefined"){},而不要去使用if(a)因?yàn)槿绻鸻不存在(未聲明)則會(huì)出錯(cuò),對(duì)于Array,Null等特殊對(duì)象使用typeof一律返回object,這正是typeof的局限性。
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
在這段代碼中,result實(shí)際上就是閉包f2函數(shù)。它一共運(yùn)行了兩次,第一次的值是999,第二次的值是1000。這證明了,函數(shù)f1中的局部變量n一直保存在內(nèi)存中,并沒有在f1調(diào)用后被自動(dòng)清除。
為什么會(huì)這樣呢?原因就在于f1是f2的父函數(shù),而f2被賦給了一個(gè)全局變量,這導(dǎo)致f2始終在內(nèi)存中,而f2的存在依賴于f1,因此f1也始終在內(nèi)存中,不會(huì)在調(diào)用結(jié)束后,被垃圾回收機(jī)制(garbage collection)回收。
這段代碼中另一個(gè)值得注意的地方,就是"nAdd=function(){n+=1}"這一行,首先在nAdd前面沒有使用var關(guān)鍵字,因此nAdd是一個(gè)全局變量,而不是局部變量。其次,nAdd的值是一個(gè)匿名函數(shù)(anonymous function),而這個(gè)匿名函數(shù)本身也是一個(gè)閉包,所以nAdd相當(dāng)于是一個(gè)setter,可以在函數(shù)外部對(duì)函數(shù)內(nèi)部的局部變量進(jìn)行操作。
立即執(zhí)行函數(shù)
立即執(zhí)行函數(shù)能配合閉包保存狀態(tài)。
像普通的函數(shù)傳參一樣,立即執(zhí)行函數(shù)也能傳參數(shù)。如果在函數(shù)內(nèi)部再定義一個(gè)函數(shù),而里面的那個(gè)函數(shù)能引用外部的變量和參數(shù)(閉包),利用這一點(diǎn),我們能使用立即執(zhí)行函數(shù)鎖住變量保存狀態(tài)。
// 并不會(huì)像你想象那樣的執(zhí)行,因?yàn)閕的值沒有被鎖住
// 當(dāng)我們點(diǎn)擊鏈接的時(shí)候,其實(shí)for循環(huán)已經(jīng)執(zhí)行完了
// 于是在點(diǎn)擊的時(shí)候i的值其實(shí)已經(jīng)是elems.length了
var elems = document.getElementsByTagName( 'a' );
for ( var i = 0; i < elems.length; i++ ) {
elems[ i ].addEventListener( 'click', function(e){
e.preventDefault();
alert( 'I am link #' + i );
}, 'false' );
}
// 這次我們得到了想要的結(jié)果
// 因?yàn)樵诹⒓磮?zhí)行函數(shù)內(nèi)部,i的值傳給了lockedIndex,并且被鎖在內(nèi)存中
// 盡管for循環(huán)結(jié)束后i的值已經(jīng)改變,但是立即執(zhí)行函數(shù)內(nèi)部lockedIndex的值并不會(huì)改變
var elems = document.getElementsByTagName( 'a' );
for ( var i = 0; i < elems.length; i++ ) {
(function( lockedInIndex ){
elems[ i ].addEventListener( 'click', function(e){
e.preventDefault();
alert( 'I am link #' + lockedInIndex );
}, 'false' );
})( i );
}
內(nèi)存泄露
function assignHandler() {
var el = document.getElementById('demo');
el.onclick = function() {
console.log(el.id);
}
}
assignHandler();
以上代碼創(chuàng)建了作為el元素事件處理程序的閉包,而這個(gè)閉包又創(chuàng)建了一個(gè)循環(huán)引用,只要匿名函數(shù)存在,el的引用數(shù)至少為1,因些它所占用的內(nèi)存就永完不會(huì)被回收。
function assignHandler() {
var el = document.getElementById('demo');
var id = el.id;
el.onclick = function() {
console.log(id);
}
el = null;
}
assignHandler();
把變量el設(shè)置null能夠解除DOM對(duì)象的引用,確保正?;厥掌湔加脙?nèi)存。
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
//問:三行a,b,c的輸出分別是什么?
//這是一道非常典型的JS閉包問題。其中嵌套了三層fun函數(shù),搞清楚每層fun的函數(shù)是那個(gè)fun函數(shù)尤為重要。
//答案: //a: undefined,0,0,0 //b: undefined,0,1,2 //c: undefined,0,1,1
JavaScript的執(zhí)行上下文生成之后,會(huì)創(chuàng)建一個(gè)叫做變量對(duì)象的特殊對(duì)象(具體會(huì)在下一篇文章與執(zhí)行上下文一起總結(jié)),JavaScript的基礎(chǔ)數(shù)據(jù)類型往往都會(huì)保存在變量對(duì)象中。
嚴(yán)格意義上來(lái)說,變量對(duì)象也是存放于堆內(nèi)存中,但是由于變量對(duì)象的特殊職能,我們?cè)诶斫鈺r(shí)仍然需要將其于堆內(nèi)存區(qū)分開來(lái)。
基礎(chǔ)數(shù)據(jù)類型都是一些簡(jiǎn)單的數(shù)據(jù)段,JavaScript中有5中基礎(chǔ)數(shù)據(jù)類型,分別是Undefined、Null、Boolean、Number、String?;A(chǔ)數(shù)據(jù)類型都是按值訪問,因?yàn)槲覀兛梢灾苯硬僮鞅4嬖谧兞恐械膶?shí)際的值。