JavaScript中的閉包

作用域

先來(lái)說(shuō)下什么是作用域五续,簡(jiǎn)單的說(shuō)迂苛,作用域就是變量與函數(shù)的可訪問(wèn)范圍,即作用域控制著變量與函數(shù)的可見性和生命周期讹蘑。他減少了名稱沖突怀樟,并且提供了自動(dòng)內(nèi)存管理功偿。
在JavaScript中,變量的作用域有全局作用域和局部作用域兩種往堡。

全局作用域

var num1 = 1;
function fun1 (){
  num2 = 2;
}

以上三個(gè)對(duì)象 num1, num2fun1 均是全局作用域械荷,這里要注意的是 末定義直接賦值的變量自動(dòng)聲明為擁有全局作用域

局部作用域

function wrap(){
  var obj = "我被wrap包裹起來(lái)了虑灰,wrap外部無(wú)法直接訪問(wèn)到我";
  function innerFun(){
      //外部無(wú)法訪問(wèn)我
  }
}

作用域鏈

當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí)吨瞎,會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈。
[{當(dāng)前環(huán)境的變量對(duì)象}穆咐,{外層變量對(duì)象}关拒,{外層的外層的變量對(duì)象}, {window全局變量對(duì)象}] 每個(gè)數(shù)組單元就是作用域鏈的一塊,這個(gè)塊就是我們的變量對(duì)象庸娱。
作用于鏈的前端,始終都是當(dāng)前執(zhí)行的代碼所在環(huán)境的變量對(duì)象谐算。全局執(zhí)行環(huán)境的變量對(duì)象也始終都是鏈的最后一個(gè)對(duì)象熟尉。

function foo(){
    var a = 12;
    fun(a);
    function fun(a){
        var b = 8;
        console.log(a + b);
    }
}  
foo();

再來(lái)看上面這個(gè)簡(jiǎn)單的例子,我們可以先思考一下洲脂,每個(gè)執(zhí)行環(huán)境下的變量對(duì)象都是什么斤儿? 這兩個(gè)函數(shù)它們的變量對(duì)象分別都是什么剧包?

我們以fun為例,當(dāng)我們調(diào)用它時(shí)往果,會(huì)創(chuàng)建一個(gè)包含 arguments疆液,ab的 ** 活動(dòng)對(duì)象 **陕贮,對(duì)于函數(shù)而言堕油,在執(zhí)行的最開始階段它的活動(dòng)對(duì)象里只包含一個(gè)變量,即arguments(當(dāng)執(zhí)行流進(jìn)入肮之,再創(chuàng)建其他的活動(dòng)對(duì)象)掉缺。

在活動(dòng)對(duì)象中,它依然表示當(dāng)前參數(shù)集合戈擒。對(duì)于函數(shù)的活動(dòng)對(duì)象眶明,我們可以想象成兩部分,一個(gè)是固定的arguments對(duì)象筐高,另一部分是函數(shù)中的局部變量搜囱。而在此例中,a和b都被算入是局部變量中柑土,即便a已經(jīng)包含在了arguments中蜀肘,但他還是屬于。

有沒有發(fā)現(xiàn)在環(huán)境棧中冰单,所有的執(zhí)行環(huán)境都可以組成相對(duì)應(yīng)的作用域鏈幌缝。我們可以在環(huán)境棧中非常直觀的拼接成一個(gè)相對(duì)作用域鏈。

下面我們大致說(shuō)下這段代碼的執(zhí)行流程:

  1. 在創(chuàng)建foo的時(shí)候诫欠,作用域鏈已經(jīng)預(yù)先包含了一個(gè)全局對(duì)象涵卵,并保存在內(nèi)部屬性[[ Scope ]]當(dāng)中。
  2. 執(zhí)行foo函數(shù)荒叼,創(chuàng)建執(zhí)行環(huán)境與活動(dòng)對(duì)象后轿偎,取出函數(shù)的內(nèi)部屬性[[Scope]]構(gòu)建當(dāng)前環(huán)境的作用域鏈(取出后,只有全局變量對(duì)象被廓,然后此時(shí)追加了一個(gè)它自己的活動(dòng)對(duì)象)坏晦。
  3. 執(zhí)行過(guò)程中遇到了fun,從而繼續(xù)對(duì)fun使用上一步的操作嫁乘。
  4. fun執(zhí)行結(jié)束昆婿,移出環(huán)境棧。foo因此也執(zhí)行完畢蜓斧,繼續(xù)移出仓蛆。
  5. javscript 監(jiān)聽到foo沒有被任何變量所引用,開始實(shí)施垃圾回收機(jī)制挎春,清空占用內(nèi)存看疙。

作用域鏈其實(shí)就是引用了當(dāng)前執(zhí)行環(huán)境的變量對(duì)象的指針列表豆拨,它只是引用,但不是包含能庆,因?yàn)樗男螤钕矜湕l施禾,它的執(zhí)行過(guò)程也非常符合,所以我們都稱之為 ** 作用域鏈 **搁胆,而當(dāng)我們弄懂了這其中的奧秘弥搞,就可以拋開這種形式上的束縛,從原理上出發(fā)丰涉。

閉包

閉包拓巧,官方對(duì)閉包的解釋是:一個(gè)擁有許多變量和綁定了這些變量的環(huán)境的表達(dá)式(通常是一個(gè)函數(shù)),因而這些變量也是該表達(dá)式的一部分一死。

閉包的特點(diǎn):

  1. 作為一個(gè)函數(shù)變量的一個(gè)引用肛度,當(dāng)函數(shù)返回時(shí),其處于激活狀態(tài)投慈。
  2. 一個(gè)閉包就是當(dāng)一個(gè)函數(shù)返回時(shí)承耿,一個(gè)沒有釋放資源的棧區(qū)。
    其實(shí)就是 ** 有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù) 伪煤。簡(jiǎn)單說(shuō)就是加袋,假設(shè)函數(shù)a是定義在函數(shù)b中的函數(shù),那么函數(shù)a就是一個(gè)閉包抱既。正常情況下职烧,在函數(shù)的外部訪問(wèn)不到函數(shù)內(nèi)部的變量,但有了閉包就可以間接的實(shí)現(xiàn)訪問(wèn)內(nèi)部變量的需要防泵。也就是說(shuō)蚀之, 閉包是連接函數(shù)內(nèi)部和外部的橋梁 **。

閉包的作用

  1. 訪問(wèn)函數(shù)內(nèi)部的變量捷泞。
  2. 讓被引用的變量值始終保持在內(nèi)存中足删。
function fn1(){
    var a = 1;
    return function(){
        console.log(++a);
    }

}

var fn2 = fn1();

fn2();        //輸出2

fn2();        //輸出3

在這段代碼中,fn1中的閉包函數(shù)被當(dāng)作結(jié)果返回锁右,在閉包中的引用的變量a因?yàn)楸灰枚鴽]有被清除失受,一直保存在內(nèi)存當(dāng)中,所以執(zhí)行fn2的時(shí)候會(huì)輸出不斷增加的結(jié)果:2和3咏瑟。

當(dāng)閉包中引用了函數(shù)中的變量時(shí)拂到,那么,這個(gè)變量就會(huì)保存在內(nèi)存中码泞。也就是上面提到的閉包的第二個(gè)作用兄旬。之所以為這樣,是因?yàn)镴avaScript的回收機(jī)制浦夷。

基本所有瀏覽器都是使用“標(biāo)記清除”的方式回收內(nèi)存辖试。也就是說(shuō),當(dāng)變量進(jìn)入執(zhí)行環(huán)境的時(shí)候(在函數(shù)中聲明一個(gè)變量)劈狐,就給變量添加標(biāo)記罐孝,而當(dāng)函數(shù)執(zhí)行完的,變量不再被引用的時(shí)候肥缔,再添加刪除的標(biāo)記莲兢,垃圾收集器就會(huì)自動(dòng)清楚這個(gè)變量占有的內(nèi)存。但在閉包中引用了函數(shù)中的變量续膳,而閉包又被當(dāng)作結(jié)果返回時(shí)改艇,閉包中的因?yàn)楸灰镁筒粫?huì)被清除

閉包的用途

  1. 匿名自執(zhí)行函數(shù)

我們知道所有的變量,如果不加上var關(guān)鍵字坟岔,則默認(rèn)的會(huì)添加到全局對(duì)象的屬性上去谒兄,這樣的臨時(shí)變量加入全局對(duì)象有很多壞處,
比如:別的函數(shù)可能誤用這些變量社付;造成全局對(duì)象過(guò)于龐大承疲,影響訪問(wèn)速度(因?yàn)樽兞康娜≈凳切枰獜脑玩溕媳闅v的)。
除了每次使用變量都是用var關(guān)鍵字外鸥咖,我們?cè)趯?shí)際情況下經(jīng)常遇到這樣一種情況燕鸽,即有的函數(shù)只需要執(zhí)行一次,其內(nèi)部變量無(wú)需維護(hù)啼辣,
比如UI的初始化啊研,那么我們可以使用閉包:

var data= {
  table : [],
  tree : {}
};

(function(dm){
  for(var i = 0; i < dm.table.rows; i++){
    var row = dm.table.rows[i];
    for(var j = 0; j < row.cells; i++){
      drawCell(i, j);
    }
  }
})(data);

我們創(chuàng)建了一個(gè)匿名的函數(shù),并立即執(zhí)行它鸥拧,由于外部無(wú)法引用它內(nèi)部的變量党远,因此在函數(shù)執(zhí)行完后會(huì)立刻釋放資源,關(guān)鍵是不污染全局對(duì)象住涉。

  1. 結(jié)果緩存

我們開發(fā)中會(huì)碰到很多情況麸锉,設(shè)想我們有一個(gè)處理過(guò)程很耗時(shí)的函數(shù)對(duì)象,每次調(diào)用都會(huì)花費(fèi)很長(zhǎng)時(shí)間舆声,

那么我們就需要將計(jì)算出來(lái)的值存儲(chǔ)起來(lái)花沉,當(dāng)調(diào)用這個(gè)函數(shù)的時(shí)候,首先在緩存中查找媳握,如果找不到碱屁,則進(jìn)行計(jì)算,然后更新緩存并返回值蛾找,如果找到了娩脾,直接返回查找到的值即可。閉包正是可以做到這一點(diǎn)打毛,因?yàn)樗粫?huì)釋放外部的引用柿赊,從而函數(shù)內(nèi)部的值可以得以保留俩功。

var CachedSearchBox = (function(){
  var cache = {},
      count = [];
  return {
    attachSearchBox : function(dsid){
      if(dsid in cache){ //如果結(jié)果在緩存中
        return cache[dsid]; //直接返回緩存中的對(duì)象
      }
      var fsb = new uikit.webctrl.SearchBox(dsid); //新建
      cache[dsid] = fsb; //更新緩存
      if(count.length > 100){ //保正緩存的大小<=100
        delete cache[count.shift()];
      }
      return fsb;
    },

    clearSearchBox : function(dsid){
      if(dsid in cache){
        cache[dsid].clearSelection();
      }
    }
  };
})();

CachedSearchBox.attachSearchBox("input");

這樣我們?cè)诘诙握{(diào)用的時(shí)候,就會(huì)從緩存中讀取到該對(duì)象碰声。

  1. 封裝
var person = function(){
  //變量作用域?yàn)楹瘮?shù)內(nèi)部诡蜓,外部無(wú)法訪問(wèn)
  var name = "default";

  return {
      getName : function(){
          return name;
      },
      setName : function(newName){
          name = newName;
      }
  }
}();

print(person.name);//直接訪問(wèn),結(jié)果為undefined
print(person.getName());
person.setName("abruzzi");
print(person.getName());

// 得到結(jié)果如下:  

// undefined  
// default  
// abruzzi
  1. 實(shí)現(xiàn)類和繼承
function Person(){
  var name = "default";

  return {
    getName : function(){
      return name;
    },
    setName : function(newName){
      name = newName;
    }
  }
};

var p = new Person();
p.setName("Tom");
alert(p.getName());

var Jack = function(){};
//繼承自Person
Jack.prototype = new Person();
//添加私有方法
Jack.prototype.Say = function(){
  alert("Hello,my name is Jack");
};
var j = new Jack();
j.setName("Jack");
j.Say();
alert(j.getName());

我們定義了Person胰挑,它就像一個(gè)類蔓罚,我們new一個(gè)Person對(duì)象,訪問(wèn)它的方法瞻颂。

下面我們定義了Jack豺谈,繼承Person,并添加自己的方法贡这。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末茬末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子藕坯,更是在濱河造成了極大的恐慌团南,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炼彪,死亡現(xiàn)場(chǎng)離奇詭異吐根,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)辐马,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門拷橘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人喜爷,你說(shuō)我怎么就攤上這事冗疮。” “怎么了檩帐?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵术幔,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我湃密,道長(zhǎng)诅挑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任泛源,我火速辦了婚禮拔妥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘达箍。我一直安慰自己没龙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著硬纤,像睡著了一般解滓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上筝家,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天伐蒂,我揣著相機(jī)與錄音,去河邊找鬼肛鹏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛恩沛,可吹牛的內(nèi)容都是我干的在扰。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼雷客,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼芒珠!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起搅裙,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤皱卓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后部逮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娜汁,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年兄朋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了掐禁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡颅和,死狀恐怖傅事,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情峡扩,我是刑警寧澤蹭越,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站教届,受9級(jí)特大地震影響响鹃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜巍佑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一茴迁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧萤衰,春花似錦堕义、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)洒擦。三九已至,卻和暖如春怕膛,著一層夾襖步出監(jiān)牢的瞬間熟嫩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工褐捻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掸茅,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓柠逞,卻偏偏與公主長(zhǎng)得像昧狮,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子板壮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容