閉包

當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時(shí),就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用 域之外執(zhí)行谨垃。

function foo() { 
  var a = 2;
  function bar() { 
    console.log( a ); // 2
  }
  bar();
}
foo();

這是閉包嗎?
技術(shù)上來講,也許是距芬。但根據(jù)前面的定義,確切地說并不是解滓。因?yàn)椴]有體現(xiàn)出bar記住了所在的詞法作用域踊谋。我認(rèn)為最準(zhǔn)確地用來解釋 bar() 對 a 的引用的方法是詞法作用域的查找規(guī)則,而這些規(guī)則只是閉包的一部分。(但卻 是非常重要的一部分!)
下面我們來看一段代碼,清晰地展示了閉包:

function foo() { 
  var a = 2;
  function bar() { 
    console.log( a );
  }
  return bar;
}
var baz = foo();
baz(); // 2 —— 朋友,這就是閉包的效果后裸。

拜 bar() 所聲明的位置所賜,它擁有涵蓋 foo() 內(nèi)部作用域的閉包,使得該作用域能夠一 直存活,以供 bar() 在之后任何時(shí)間進(jìn)行引用辙诞。
bar() 依然持有對該作用域的引用,而這個(gè)引用就叫作閉包。
因此,在幾微秒之后變量 baz 被實(shí)際調(diào)用(調(diào)用內(nèi)部函數(shù) bar),不出意料它可以訪問定義時(shí)的詞法作用域,因此它也可以如預(yù)期般訪問變量 a轻抱。

當(dāng)然,無論使用何種方式對函數(shù)類型的值進(jìn)行傳遞,當(dāng)函數(shù)在別處被調(diào)用時(shí)都可以觀察到 閉包。

function foo() {
  var a = 2;
  function baz() { 
    console.log( a ); // 2
  }
  bar( baz );
 }
function bar(fn) {
fn(); // 媽媽快看呀,這就是閉包!   bar函數(shù)居然訪問到了foo函數(shù)的作用域旦部。
}

把內(nèi)部函數(shù) baz 傳遞給 bar,當(dāng)調(diào)用這個(gè)內(nèi)部函數(shù)時(shí)(現(xiàn)在叫作 fn),它涵蓋的 foo() 內(nèi)部
作用域的閉包就可以觀察到了,因?yàn)樗軌蛟L問 a了祈搜。等于在bar的作用域中讀取到foo的作用域,所以產(chǎn)生了閉包士八。
來看看平常寫代碼遇到的閉包

function wait(msg) {
        setTimeout(function timer(){
            console.log(msg)
        },1000)
    }
wait("hello")

這怎么就是閉包了呢容燕?
沒關(guān)系,如果這個(gè)看不出來的話婚度,我們不妨把代碼變化一下

function wait(msg) {
        function timer(){
            console.log(msg)
        }
        setTimeout(timer,1000)
    }
wait("hello")

如果還是理解不了的話蘸秘。我們再來舉出一個(gè)例子

function wait(msg) {
        function timer(){
            console.log(msg)
        }
        fn(timer)
    }
    function fn(fnc){
        fnc();
    }
    wait("hello")

這就能看出來是如何使用的閉包了吧。
將一個(gè)內(nèi)部函數(shù)(名為 timer)傳遞給 setTimeout(..)蝗茁。timer 具有涵蓋 wait(..) 作用域的閉包,因此還保有對變量 msg 的引用醋虏。
類似的還有

function setupBot(name, selector) {
    $( selector ).click( function activator() {
             console.log( "Activating: " + name );
    } );
}
setupBot( "Closure Bot 1", "#bot_1" );
setupBot( "Closure Bot 2", "#bot_2" );

使得 $( selector ).click 函數(shù)內(nèi)部可以讀取setupBot函數(shù)的作用域。

傳遞函數(shù)當(dāng)然也可以是間接的哮翘。

var fn;
function foo() {
  var a = 2;
  function baz() { 
    console.log( a );
  }
  fn = baz; //將baz分配給全局變量
}
function bar() {
  fn(); // 媽媽快看呀,這就是閉包!
}
foo();
bar(); // 2

無論通過何種手段將內(nèi)部函數(shù)傳遞到所在的詞法作用域以外,它都會持有對原始定義作用 域的引用,無論在何處執(zhí)行這個(gè)函數(shù)都會使用閉包颈嚼。本質(zhì)上無論何時(shí)何地,如果將函數(shù)(訪問它們各自的詞法作用域)當(dāng)作第一 級的值類型并到處傳遞,你就會看到閉包在這些函數(shù)中的應(yīng)用。在定時(shí)器饭寺、事件監(jiān)聽器阻课、 Ajax請求叫挟、跨窗口通信、Web Workers或者任何其他的異步(或者同步)任務(wù)中,只要使 用了回調(diào)函數(shù),實(shí)際上就是在使用閉包!

循環(huán)和閉包

for (var i=1; i<=5; i++) {
 setTimeout( function timer() {
      console.log( i );
  }, i*1000 );
}

為什么打印5個(gè)6
仔細(xì)想一下,這好像又是顯而易見的,延遲函數(shù)的回調(diào)會在循環(huán)結(jié)束時(shí)才執(zhí)行限煞。事實(shí)上, 當(dāng)定時(shí)器運(yùn)行時(shí)即使每個(gè)迭代中執(zhí)行的是setTimeout(.., 0),所有的回調(diào)函數(shù)依然是在循 環(huán)結(jié)束后才會被執(zhí)行,因此會每次輸出一個(gè) 6 出來抹恳。
這里引伸出一個(gè)更深入的問題,代碼中到底有什么缺陷導(dǎo)致它的行為同語義所暗示的不一 致呢?
缺陷是我們試圖假設(shè)循環(huán)中的每個(gè)迭代在運(yùn)行時(shí)都會給自己“捕獲”一個(gè) i 的副本。但是 根據(jù)作用域的工作原理,實(shí)際情況是盡管循環(huán)中的五個(gè)函數(shù)是在各個(gè)迭代中分別定義的, 但是它們都被封閉在一個(gè)共享的全局作用域中,因此實(shí)際上只有一個(gè) i署驻。
通過聲明并立即執(zhí)行一個(gè)函數(shù)來創(chuàng)建作用域奋献。

for (var i=1; i<=5; i++) { 
  (function() {
    var j = i;
    setTimeout( function timer() {
       console.log( j );
     }, j*1000 );
  })(); 
}

構(gòu)造函數(shù)和閉包

構(gòu)造函數(shù)可以通過new 來調(diào)用產(chǎn)生對象。每次new都會產(chǎn)生一個(gè)新的作用域硕舆。這個(gè)作用域在哪呢秽荞?我們先看看new的過程是什么

var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj) 

JS中一般產(chǎn)生作用域的為函數(shù)塊(當(dāng)然let,catch塊也可以產(chǎn)生作用域)抚官。所以每一個(gè)Base.call(obj) 都會產(chǎn)生一個(gè)作用域扬跋。如果不存在閉包的話,這個(gè)作用域很快就會被釋放凌节。那怎么產(chǎn)生閉包呢钦听?

function MyObject(){
   //私有變量或私有函數(shù)(使用var 定義的變量)
   var privateVariable=10;
  // 特權(quán)方法 (掛在this上的變量)
  this.publicMethod=function(){
     privateVariable++;
  };

}
var obj1 = new MyObject();
var obj2 = new MyObject();

obj1 和 obj2都可以通過publicMethod去修改privateVariable變量。但是這兩個(gè)變量之間沒有聯(lián)系倍奢,是單獨(dú)存在的朴上。因?yàn)閚ew的操作使obj1對象和obj2對象都有了個(gè)屬性publicMethod可以訪問到privateVariable變量,就使每次的MyObject作用域不能釋放卒煞,造成了閉包痪宰。
在構(gòu)造函數(shù)內(nèi)部定義了所有私有變量和函數(shù),又繼續(xù)創(chuàng)建了能夠訪問這些私有成員的特權(quán)方法畔裕。能在構(gòu)造函數(shù)中定義特權(quán)方法是因?yàn)樘貦?quán)方法作為閉包有權(quán)訪問在構(gòu)造函數(shù)中定義的所有變量和函數(shù)衣撬。
構(gòu)造函數(shù)的閉包

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扮饶,隨后出現(xiàn)的幾起案子具练,更是在濱河造成了極大的恐慌,老刑警劉巖甜无,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扛点,死亡現(xiàn)場離奇詭異,居然都是意外死亡岂丘,警方通過查閱死者的電腦和手機(jī)陵究,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奥帘,“玉大人畔乙,你說我怎么就攤上這事◆娓牛” “怎么了牲距?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵返咱,是天一觀的道長。 經(jīng)常有香客問我牍鞠,道長咖摹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任难述,我火速辦了婚禮萤晴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胁后。我一直安慰自己店读,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布攀芯。 她就那樣靜靜地躺著屯断,像睡著了一般。 火紅的嫁衣襯著肌膚如雪侣诺。 梳的紋絲不亂的頭發(fā)上殖演,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機(jī)與錄音年鸳,去河邊找鬼趴久。 笑死,一個(gè)胖子當(dāng)著我的面吹牛搔确,可吹牛的內(nèi)容都是我干的彼棍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼膳算,長吁一口氣:“原來是場噩夢啊……” “哼滥酥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起畦幢,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缆蝉,沒想到半個(gè)月后宇葱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刊头,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年黍瞧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片原杂。...
    茶點(diǎn)故事閱讀 40,498評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡印颤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出穿肄,到底是詐尸還是另有隱情年局,我是刑警寧澤际看,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站矢否,受9級特大地震影響仲闽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜僵朗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一赖欣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧验庙,春花似錦顶吮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至汗菜,卻和暖如春让禀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背陨界。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工巡揍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人菌瘪。 一個(gè)月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓腮敌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親俏扩。 傳聞我的和親對象是個(gè)殘疾皇子糜工,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評論 2 359

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