js閉包詳解

1.什么是閉包吱瘩?

要了解什么是閉包宛官,首先你要了解作用域。

js的作用域分兩種凶杖,全局作用域和局部作用域胁艰。

我們知道在js作用域環(huán)境中訪問變量的順序是由內(nèi)向外的,內(nèi)部作用域可以獲得當(dāng)前作用域下的變量和當(dāng)前作用域的外層作用域下的變量智蝠,反之則不能腾么,也就是說在外層作用域下無法獲取內(nèi)層作用域下的變量,同樣在不同的函數(shù)作用域中也是不能相互訪問彼此變量的杈湾。
那么我們想在一個函數(shù)內(nèi)部也有限權(quán)訪問另一個函數(shù)內(nèi)部的變量該怎么辦呢解虱?

閉包就是用來解決這一需求的,閉包的本質(zhì)就是在一個函數(shù)內(nèi)部創(chuàng)建另一個函數(shù)漆撞。
簡單講殴泰,閉包就是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)。

2.閉包的特性

  1. 函數(shù)嵌套函數(shù)
  2. 函數(shù)內(nèi)部可以引用函數(shù)外部的參數(shù)和變量
  3. 參數(shù)和變量不會被垃圾回收機(jī)制回收

3.閉包解析

1.簡單的閉包案例
function fn1() {
    var name = 'lisi';
    function fn2() {
        console.log(name);
    }
    fn2();
}
fn1();

因為fn2函數(shù)執(zhí)行時浮驳,會向外層找變量name悍汛,訪問到fn1的變量name,fn2本身是個函數(shù)至会,滿足’fn2函數(shù)訪問到fn1函數(shù)的變量‘离咐,所有由此產(chǎn)生了閉包

2.為什么閉包函數(shù)能夠訪問其他函數(shù)的作用域 ?

從堆棧的角度看待js函數(shù)

基本變量的值一般都是存在棧內(nèi)存中,而對象類型的變量的值存儲在堆內(nèi)存中奋献,棧內(nèi)存存儲對應(yīng)空間地址健霹⊥希基本的數(shù)據(jù)類型: Number 、Boolean糖埋、Undefined宣吱、String、Null瞳别。

js棧與堆詳情內(nèi)容請參考另一篇文章(js中的棧內(nèi)存與堆內(nèi)存)

 // a 是一個對象
var  a= {b: 10 }  

當(dāng)我們執(zhí)行 a={b:20}時征候,堆內(nèi)存就會產(chǎn)生新的對象{b:20},棧內(nèi)存中a的指針會指向新的空間地址( {b:20} )祟敛,而堆內(nèi)存中原來的{b:10}就會被程序引擎垃圾回收掉疤坝,節(jié)約內(nèi)存空間。
我們知道js函數(shù)也是對象馆铁,它也是在堆與棧內(nèi)存中存儲的跑揉,我們來看一下轉(zhuǎn)化:

var a = 1;
function fn(){
    var b = 2;
    function fn1(){
        console.log(b);
    }
    fn1();
}
fn();
image

由于棧是一種先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu),所以全局執(zhí)行環(huán)境在最底層,我們由底自上看

  1. 在全局執(zhí)行環(huán)境(瀏覽器就是window作用域)埠巨,全局作用域里有個變量a及函數(shù)對象fn
  2. 進(jìn)入fn历谍,此時棧內(nèi)存就會開辟一個fn的執(zhí)行環(huán)境,這個環(huán)境里有變量b和函數(shù)對象fn1辣垒,這里可以訪問自身執(zhí)行環(huán)境和全局執(zhí)行環(huán)境所定義的變量a
  3. 進(jìn)入fn1望侈,此時棧內(nèi)存就會開辟 一個fn1的執(zhí)行環(huán)境,這里面沒有定義其他變量勋桶,但是我們可以訪問到fn和全局執(zhí)行環(huán)境里面的變量a脱衙,因為程序在訪問變量時,是向底層棧一個個找例驹,如果找到全局執(zhí)行環(huán)境里都沒有對應(yīng)變量捐韩,則程序拋出underfined的錯誤。
  4. 隨著fn1()執(zhí)行完畢眠饮,fn1的執(zhí)行環(huán)境被杯銷毀奥帘,接著執(zhí)行完fn()铜邮,fn的執(zhí)行環(huán)境也會被銷毀仪召,只剩全局的執(zhí)行環(huán)境下,現(xiàn)在沒有b變量松蒜,和fn1函數(shù)對象了扔茅,只有a 和 fn(函數(shù)聲明作用域是window下)

在函數(shù)內(nèi)訪問某個變量是根據(jù)函數(shù)作用域鏈來判斷變量是否存在的,而函數(shù)作用域鏈?zhǔn)浅绦蚋鶕?jù)函數(shù)所在的執(zhí)行環(huán)境棧來初始化的秸苗,所以上面的例子召娜,我們在fn1里面打印變量b,根據(jù)fn1的作用域鏈的找到對應(yīng)fn執(zhí)行環(huán)境下的變量b惊楼。所以當(dāng)程序在調(diào)用某個函數(shù)時玖瘸,做了一下的工作:準(zhǔn)備執(zhí)行環(huán)境秸讹,初始函數(shù)作用域鏈和arguments參數(shù)對象

3.閉包應(yīng)用

1.定時器與for循環(huán)
for (var i = 1; i <= 10; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

在這段代碼中,我們對其的預(yù)期是輸出1~10雅倒,但卻輸出10次11璃诀。這是因為 i 是聲明在全局作用中的,定時器中的匿名函數(shù)也是執(zhí)行在全局作用域中蔑匣,當(dāng)循環(huán)結(jié)束時劣欢,i 已經(jīng)是 11 了,所以每次都輸出11了裁良。

怎么解決這個問題呢凿将?

// 解決方法1: 使用es6語法,let聲明i
for (let i = 1; i <= 10; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}
// 解決方法2:立即執(zhí)行函數(shù)IIFE
// 我們可以讓i在每次迭代的時候价脾,都產(chǎn)生一個私有的作用域牧抵,在這個私有的作用域中保存當(dāng)前i的值。
for (var i = 1; i <= 10; i++) {
    (function () {
        var j = i;
        setTimeout(function () {
            console.log(j);
        }, 1000);
    })();
}
2.this指向問題
var object = {
     name: "object",
     getName: function() {
        return function() {
             console.info(this.name)
        }
    }
}
object.getName()()    // underfined
// 因為里面的閉包函數(shù)是在window作用域下執(zhí)行的侨把,也就是說灭忠,this指向windows
3.內(nèi)存泄露
function  showId() {
    var el = document.getElementById("app")
    el.onclick = function(){
      aler(el.id)   // 這樣會導(dǎo)致閉包引用外層的el,當(dāng)執(zhí)行完showId后座硕,el無法釋放
    }
}

// 改成下面
function  showId() {
    var el = document.getElementById("app")
    var id  = el.id
    el.onclick = function(){
      aler(id)   // 這樣會導(dǎo)致閉包引用外層的el弛作,當(dāng)執(zhí)行完showId后,el無法釋放
    }
    el = null    // 主動釋放el
}
4.遞歸調(diào)用
function  factorial(num) {
   if(num<= 1) {
       return 1;
   } else {
      return num * factorial(num-1)
   }
}
var anotherFactorial = factorial
factorial = null
anotherFactorial(4)   // 報錯 华匾,因為最好是return num* arguments.callee(num-1)映琳,arguments.callee指向當(dāng)前執(zhí)行函數(shù),但是在嚴(yán)格模式下不能使用該屬性也會報錯蜘拉,所以借助閉包來實現(xiàn)


// 使用閉包實現(xiàn)遞歸
function newFactorial = (function f(num){
    if(num<1) {return 1}
    else {
       return num* f(num-1)
    }
}) //這樣就沒有問題了萨西,實際上起作用的是閉包函數(shù)f,而不是外面的函數(shù)newFactorial

4.閉包的好與壞

好處

  1. 保護(hù)函數(shù)內(nèi)的變量安全 旭旭,實現(xiàn)封裝谎脯,防止變量流入其他環(huán)境發(fā)生命名沖突
  2. 在內(nèi)存中維持一個變量,可以做緩存(但使用多了同時也是一項缺點持寄,消耗內(nèi)存)
  3. 匿名自執(zhí)行函數(shù)可以減少內(nèi)存消耗

壞處

  1. 其中一點上面已經(jīng)有體現(xiàn)了源梭,就是被引用的私有變量不能被銷毀,增大了內(nèi)存消耗稍味,造成內(nèi)存泄漏废麻,解決方法是可以在使用完變量后手動為它賦值為null;
  2. 其次由于閉包涉及跨域訪問模庐,所以會導(dǎo)致性能損失烛愧,我們可以通過把跨作用域變量存儲在局部變量中,然后直接訪問局部變量,來減輕對執(zhí)行速度的影響
如果不是因為某些特殊任務(wù)而需要閉包怜姿,在沒有必要的情況下慎冤,在其它函數(shù)中創(chuàng)建函數(shù)是不明智的,因為閉包對腳本性能具有負(fù)面影響沧卢,包括處理速度和內(nèi)存消耗粪薛。

5.閉包例題檢驗:

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);        // ?

var b = fun(0).fun(1).fun(2).fun(3);  // ?

var c = fun(0).fun(1);  // ?
c.fun(2);        // ?
c.fun(3);        // ?

undefined
0
0
0
undefined, 0, 1, 2
undefined, 0
1
1

文章參考: http://www.reibang.com/p/26c81fde22fb

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市搏恤,隨后出現(xiàn)的幾起案子违寿,更是在濱河造成了極大的恐慌,老刑警劉巖熟空,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藤巢,死亡現(xiàn)場離奇詭異,居然都是意外死亡息罗,警方通過查閱死者的電腦和手機(jī)掂咒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迈喉,“玉大人绍刮,你說我怎么就攤上這事“っ” “怎么了孩革?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長得运。 經(jīng)常有香客問我膝蜈,道長,這世上最難降的妖魔是什么熔掺? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任饱搏,我火速辦了婚禮,結(jié)果婚禮上置逻,老公的妹妹穿的比我還像新娘推沸。我一直安慰自己,他們只是感情好券坞,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布鬓催。 她就那樣靜靜地躺著,像睡著了一般报慕。 火紅的嫁衣襯著肌膚如雪深浮。 梳的紋絲不亂的頭發(fā)上压怠,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天眠冈,我揣著相機(jī)與錄音,去河邊找鬼。 笑死蜗顽,一個胖子當(dāng)著我的面吹牛布卡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播雇盖,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼忿等,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了崔挖?” 一聲冷哼從身側(cè)響起贸街,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎狸相,沒想到半個月后薛匪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡脓鹃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年逸尖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瘸右。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡娇跟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出太颤,到底是詐尸還是另有隱情苞俘,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布龄章,位于F島的核電站苗胀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏瓦堵。R本人自食惡果不足惜基协,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望菇用。 院中可真熱鬧澜驮,春花似錦、人聲如沸惋鸥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卦绣。三九已至耐量,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間滤港,已是汗流浹背廊蜒。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工趴拧, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人山叮。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓著榴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親屁倔。 傳聞我的和親對象是個殘疾皇子脑又,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354

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

  • 目錄 1.執(zhí)行環(huán)境與作用域鏈 2. 立即執(zhí)行函數(shù) 3. 閉包知識點 3.1 什么是閉包 3.2 使用閉包的意義與注...
    犯迷糊的小羊閱讀 636評論 0 11
  • 閉包(Closure)是前端開發(fā)者經(jīng)常會聽到的一個概念,也是我們在求職面試中經(jīng)常會遇到的題目之一锐借。透過表象去理解閉...
    淘淘笙悅閱讀 1,577評論 0 12
  • 本文摘錄及參考自:1. 學(xué)習(xí)Javascript閉包(Closure)2. 閉包的秘密3. JavaScript ...
    chenhong_f1e2閱讀 444評論 0 2
  • 如果要了解閉包问麸,我們需要先了解閉包的由來,閉包的產(chǎn)生钞翔,源于JS的詞法作用域 詞法作用域 作用域是指一個 變量能夠訪...
    羊烊羴閱讀 239評論 0 2
  • 時間是用來流浪的口叙,身軀是用來相愛的,生命是用來遺忘的嗅战,而靈魂妄田,是用來歌唱的。------吉普賽民歌 我總是喜歡那些...
    泡菜鍋閱讀 193評論 0 0