閉包的理解
因為內(nèi)部函數(shù)在被創(chuàng)建時,其作用域鏈對外部函數(shù)對應(yīng)的變量對象存在一個引用,而JS采用引用計數(shù)的方法進(jìn)行內(nèi)存管理,所以當(dāng)外部函數(shù)被執(zhí)行完畢后慎菲,其對應(yīng)的變量對象不會被回收,這樣就發(fā)生了閉包锨并,在外部函數(shù)執(zhí)行完畢后钧嘶,我們在內(nèi)部函數(shù)中仍然可以訪問外部函數(shù)作用域中的變量。
閉包就是函數(shù)的局部變量集合琳疏,只是這些局部變量在函數(shù)返回后會繼續(xù)存在有决。
閉包就是就是函數(shù)的“堆棧”在函數(shù)返回后并不釋放空盼,我們也可以理解為這些函數(shù)堆棧并不在棧上分配而是在堆上分配,當(dāng)在一個函數(shù)內(nèi)定義另外一個函數(shù)就會產(chǎn)生閉包而當(dāng)一個函數(shù)中的變量或者函數(shù)有權(quán)訪問另一個函數(shù)作用域中的變量或者函數(shù)時就產(chǎn)生了閉包了
嵌套的函數(shù)定義
我們只能通過變通的辦法來訪問函數(shù)的局部變量书幕,一般來說,這個變通的辦法就是在函數(shù)內(nèi)部再定義一個函數(shù)揽趾,因為一個函數(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 語言中,閉包的定義可以簡化為嵌套定義在函數(shù)內(nèi)部的函數(shù)粱挡。
instanceof和typeof都能用來判斷一個變量是否為空或是什么類型的變量赠幕。typeof用以獲取一個變量的類型,typeof一般只能返回如下幾個結(jié)果:number,boolean,string,function,object,undefined询筏。我們可以使用typeof來獲取一個變量是否存在榕堰,如if(typeof a!="undefined"){},而不要去使用if(a)因為如果a不存在(未聲明)則會出錯嫌套,對于Array,Null等特殊對象使用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實際上就是閉包f2函數(shù)康二。它一共運行了兩次,第一次的值是999勇蝙,第二次的值是1000沫勿。這證明了,函數(shù)f1中的局部變量n一直保存在內(nèi)存中味混,并沒有在f1調(diào)用后被自動清除产雹。
為什么會這樣呢?原因就在于f1是f2的父函數(shù)翁锡,而f2被賦給了一個全局變量蔓挖,這導(dǎo)致f2始終在內(nèi)存中,而f2的存在依賴于f1馆衔,因此f1也始終在內(nèi)存中瘟判,不會在調(diào)用結(jié)束后,被垃圾回收機(jī)制(garbage collection)回收角溃。
這段代碼中另一個值得注意的地方拷获,就是"nAdd=function(){n+=1}"這一行,首先在nAdd前面沒有使用var關(guān)鍵字减细,因此nAdd是一個全局變量匆瓜,而不是局部變量。其次未蝌,nAdd的值是一個匿名函數(shù)(anonymous function)驮吱,而這個匿名函數(shù)本身也是一個閉包,所以nAdd相當(dāng)于是一個setter萧吠,可以在函數(shù)外部對函數(shù)內(nèi)部的局部變量進(jìn)行操作左冬。
立即執(zhí)行函數(shù)
立即執(zhí)行函數(shù)能配合閉包保存狀態(tài)。
像普通的函數(shù)傳參一樣纸型,立即執(zhí)行函數(shù)也能傳參數(shù)又碌。如果在函數(shù)內(nèi)部再定義一個函數(shù),而里面的那個函數(shù)能引用外部的變量和參數(shù)(閉包)绊袋,利用這一點毕匀,我們能使用立即執(zhí)行函數(shù)鎖住變量保存狀態(tài)。
// 并不會像你想象那樣的執(zhí)行癌别,因為i的值沒有被鎖住
// 當(dāng)我們點擊鏈接的時候皂岔,其實for循環(huán)已經(jīng)執(zhí)行完了
// 于是在點擊的時候i的值其實已經(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é)果
// 因為在立即執(zhí)行函數(shù)內(nèi)部,i的值傳給了lockedIndex展姐,并且被鎖在內(nèi)存中
// 盡管for循環(huán)結(jié)束后i的值已經(jīng)改變躁垛,但是立即執(zhí)行函數(shù)內(nèi)部lockedIndex的值并不會改變
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元素事件處理程序的閉包剖毯,而這個閉包又創(chuàng)建了一個循環(huán)引用,只要匿名函數(shù)存在教馆,el的引用數(shù)至少為1逊谋,因些它所占用的內(nèi)存就永完不會被回收。
function assignHandler() {
var el = document.getElementById('demo');
var id = el.id;
el.onclick = function() {
console.log(id);
}
el = null;
}
assignHandler();
把變量el設(shè)置null能夠解除DOM對象的引用土铺,確保正辰鹤蹋回收其占用內(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ù)是那個fun函數(shù)尤為重要后德。
//答案: //a: undefined,0,0,0 //b: undefined,0,1,2 //c: undefined,0,1,1
JavaScript的執(zhí)行上下文生成之后部宿,會創(chuàng)建一個叫做變量對象的特殊對象(具體會在下一篇文章與執(zhí)行上下文一起總結(jié)),JavaScript的基礎(chǔ)數(shù)據(jù)類型往往都會保存在變量對象中瓢湃。
嚴(yán)格意義上來說理张,變量對象也是存放于堆內(nèi)存中,但是由于變量對象的特殊職能绵患,我們在理解時仍然需要將其于堆內(nèi)存區(qū)分開來涯穷。
基礎(chǔ)數(shù)據(jù)類型都是一些簡單的數(shù)據(jù)段,JavaScript中有5中基礎(chǔ)數(shù)據(jù)類型藏雏,分別是Undefined拷况、Null、Boolean掘殴、Number赚瘦、String∽嗾基礎(chǔ)數(shù)據(jù)類型都是按值訪問起意,因為我們可以直接操作保存在變量中的實際的值。