js 閉包

閉包的理解

因?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í)際的值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 閉包(closure)是Javascript語(yǔ)言的一個(gè)難點(diǎn),也是它的特色,很多高級(jí)應(yīng)用都要依靠閉包實(shí)現(xiàn)。 一、變量...
    zock閱讀 1,121評(píng)論 2 6
  • 一、變量的作用域要理解閉包,首先必須理解Javascript特殊的變量作用域。變量的作用域無(wú)非就是兩種:全局變量和...
    Bigbang_boy閱讀 213評(píng)論 0 0
  • 一.變量的作用域 要理解閉包,首先必須理解JavaScript特殊的變量作用域。 作用域無(wú)非就是兩種:全局作用域和...
    倔強(qiáng)的仙人掌閱讀 180評(píng)論 0 0
  • 閉包(closure)是Javascript語(yǔ)言的一個(gè)難點(diǎn),也是它的特色,很多高級(jí)應(yīng)用都要依靠閉包實(shí)現(xiàn)。 要理解閉...
    青春前行閱讀 304評(píng)論 0 0
  • 1 概述 負(fù)載均衡集群設(shè)計(jì)時(shí)要注意兩點(diǎn):一是否需要會(huì)話保持;二是否需要共享存儲(chǔ),共享存儲(chǔ)分為NAS,SAN,DS(...
    ghbsunny閱讀 1,636評(píng)論 0 0

友情鏈接更多精彩內(nèi)容