閉包與立即執(zhí)行函數(shù)

在學(xué)習(xí)js的過程中,我們都會遇到閉包和立即執(zhí)行函數(shù)的相關(guān)概念,今天就這兩個概念做一個大致的整理贩毕。

本文結(jié)構(gòu):

  • 閉包
    1. 閉包的概念
  • 立即執(zhí)行函數(shù)
    1. 什么是立即執(zhí)行函數(shù)
    2. 一個經(jīng)典面試題
    3. es6 中的 let

一些己、閉包

我們先來看閉包的作用:閉包通常用來創(chuàng)建內(nèi)部變量,使得這些變量不能被外部隨意修改潜必,同時又可以通過指定的函數(shù)接口來操作。

對于不理解閉包的朋友來說沃但,上面這句話并沒有什么用磁滚。所以我們通過一個簡單的例子來理解這個概念。

比如說我們現(xiàn)在要寫一個游戲宵晚,每個人初始都有5點精力垂攘,贏了就可以加1點精力,輸了就要減1點精力淤刃。

var amount=5;  //初始有5點精力
function add(){  //增加精力
  amount+=1;
  return amount;
}
function minus(){  //減少精力
  amount-=1;
  return amount;
}

//第一局贏了晒他,調(diào)用增加精力函數(shù)
add();console.log(amount);  //結(jié)果為6

//第二局輸了,調(diào)用減少精力函數(shù)
minus();console.log(amount);  //結(jié)果為5

這樣就初步實現(xiàn)了我們想要的效果钝凶。

但是仪芒,這種做法存在這樣一個問題: 由于amount暴露在全局作用域下唁影,是一個全局變量,所以任何人都可以隨意修改我的精力值掂名。如果我們不小心在另一個js文件里也聲明了amount這樣一個全局變量并給它賦值据沈,那么我們這里的amount就可能被覆蓋。

我們當(dāng)前不希望amount被覆蓋饺蔑,我們希望amount能“私有”一些锌介,因此,我們對amount提出了兩點要求:

  1. 它不能被外部隨意修改猾警;
  2. 需要能通過指定的方式去操作它孔祸。

首先,我們把上述代碼包在一個函數(shù)里:

function xxx(){
  var amount=5;
  function add(){
    amount+=1;
    return amount;
  }
  function minus(){
    amount-=1;
    return amount;
  }
}

這樣发皿,amount的作用域就變?yōu)榱撕瘮?shù)xxx的內(nèi)部崔慧,在函數(shù)外面無法訪問,這樣就實現(xiàn)了“不能被外部隨意修改”第一個要求穴墅。

但是惶室,在函數(shù)xxx的外部,我們完全操作不到amount玄货。因此皇钞,我們還需要給函數(shù)xxx留一個接口,讓我們能夠在函數(shù)外部去操作它松捉。這該怎么辦呢夹界?

經(jīng)過一番苦思冥想,我們終于想到隘世,是否可以利用函數(shù)的返回值呢可柿?

function xxx(){
  var amount=5;
  function add(){
    amount+=1;
    console.log(amount);  //實時打印amount的當(dāng)前值
    return amount;
  }
  function minus(){
    amount-=1;
    console.log(amount);  //實時打印amount的當(dāng)前值
    return amount;
  }
  return add;  //為該函數(shù)添加一個返回值,返回add函數(shù)
}

然后調(diào)用函數(shù)xxx:

var outer_add=xxx();
outer_add();   //結(jié)果為6
outer_add();   //結(jié)果為7

我們發(fā)現(xiàn)丙者,amount的值改變了趾痘,我們實現(xiàn)了我們想要實現(xiàn)的效果。

當(dāng)調(diào)用函數(shù)xxx時蔓钟,我們將xxx內(nèi)部的add函數(shù)作為操作amount的一個接口返回,outer_add函數(shù)和xxx內(nèi)部的add函數(shù)實際上是“一模一樣”的(因為它們都指向同一塊內(nèi)存)卵贱,而函數(shù)add可以訪問amount滥沫,因此我們可以通過outer_add來訪問amount。

需要注意的是键俱,由于內(nèi)部函數(shù)add在被訪問后一直處于被引用狀態(tài)兰绣,不能夠被垃圾回收。一旦我們不再使用outer_add函數(shù)编振,可以將其設(shè)置為null 缀辩,使其內(nèi)存得到釋放。

實際上,這就是閉包的典型用法⊥涡現(xiàn)在我們再回頭看看開頭提到的閉包的作用瓢阴,是否理解一些了呢?

細(xì)心的朋友可能已經(jīng)發(fā)現(xiàn)了健无,按這種做法荣恐,我們只能返回一個函數(shù)add,那函數(shù)minus怎么辦呢累贤?

我們這樣修改:

function xxx(){
  var amount=5;
  function add(){
    amount+=1;
    console.log(amount);
    return amount;
  }
  function minus(){
    amount-=1;
    console.log(amount);
    return amount;
  }
  return {
    outer_add:add,
    outer_minus:minus
  }
}

然后調(diào)用函數(shù)xxx:

var outer=xxx();
outer.outer_add();   //結(jié)果為6
outer.outer_add();   //結(jié)果為7
outer.outer_minus();   //結(jié)果為6

這樣叠穆,我們雖然不能直接操作amount這個局部變量,但可以通過函數(shù)xxx暴露出的函數(shù)outer_add和outer_minus來間接操作amount臼膏。

我們也可以這樣修改:

function xxx(){
  var amount=5;
  function add(){
    amount+=1;
    console.log(amount);
    return amount;
  }
  function minus(){
    amount-=1;
    console.log(amount);
    return amount;
  }
  window.outer_add=add;  //暴露outer_add函數(shù)硼被,可間接訪問amount
  window.outer_minus=minus;  //暴露outer_minus函數(shù),可間接訪問amount
}

然后調(diào)用函數(shù)xxx:

xxx();
outer_add();   //結(jié)果為6
outer_add();   //結(jié)果為7
outer_minus();   //結(jié)果為6

結(jié)果相同渗磅。


二嚷硫、立即執(zhí)行函數(shù)

1、什么是立即執(zhí)行函數(shù)

立即執(zhí)行函數(shù)夺溢,顧名思義论巍,聲明一個函數(shù),并立即執(zhí)行它风响。在上面的代碼中嘉汰,我們定義了一個函數(shù)xxx,并調(diào)用了它状勤。這個函數(shù)用于頁面初始化鞋怀,并且只會調(diào)用一次,因此我們可以使用立即執(zhí)行函數(shù)來代替函數(shù)xxx持搜。

改寫代碼如下:

function(){  //聲明了一個匿名函數(shù)
  var amount=5;
  function add(){
    amount+=1;
    console.log(amount);
    return amount;
  }
  function minus(){
    amount-=1;
    console.log(amount);
    return amount;
  }
  window.outer_add=add;  //暴露outer_add函數(shù)密似,可間接訪問amount
  window.outer_minus=minus;  //暴露outer_minus函數(shù),可間接訪問amount
}();  //立即調(diào)用這個匿名函數(shù)

但是我們發(fā)現(xiàn)葫盼,瀏覽器竟然報錯了残腌。

首先我們區(qū)分一下函數(shù)聲明和函數(shù)表達式:區(qū)分函數(shù)聲明和函數(shù)表達式最簡單的方法是看function關(guān)鍵字出現(xiàn)在聲明中的位置。如果function是聲明中的第一個詞贫导,那么就是一個函數(shù)聲明抛猫,否則就是一個函數(shù)表達式。

所以孩灯,上述代碼之所以報錯闺金,是因為瀏覽器認(rèn)為函數(shù)聲明后再加 “(” 是錯誤的。所以我們需要通過加()峰档、+败匹、-寨昙、~、!等運算符掀亩,使上面的代碼變?yōu)楹瘮?shù)表達式舔哪,這樣就不會報錯了,如:

(function(){
  var amount=5;
  function add(){
    amount+=1;
    console.log(amount);
    return amount;
  }
  function minus(){
    amount-=1;
    console.log(amount);
    return amount;
  }
  window.outer_add=add;  //暴露outer_add函數(shù)归榕,可間接訪問amount
  window.outer_minus=minus;  //暴露outer_minus函數(shù)尸红,可間接訪問amount
}());

然后我們就可以操作outer_add和outer_minus了:

outer_add();   //結(jié)果為6
outer_add();   //結(jié)果為7
outer_minus();   //結(jié)果為6

這就是立即執(zhí)行函數(shù)的作用,創(chuàng)建一個局部作用域刹泄,使不想暴露在全局的變量變?yōu)榫植孔兞俊?/p>

2外里、一個經(jīng)典面試題

題目如下:

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

結(jié)果:1s后,連續(xù)打印出5個5特石。

但是我們的本意是1s后分別打印出0盅蝗、1、2姆蘸、3墩莫、4,這時逞敷,我們就可以利用閉包和立即執(zhí)行函數(shù):

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

結(jié)果:1s后狂秦,連續(xù)打印出0、1推捐、2裂问、3、4牛柒。

3堪簿、es6中的let

因為let聲明的變量是塊級作用域變量(而var聲明的變量為函數(shù)作用域變量),因此皮壁,我們可以利用let的特性來創(chuàng)建一個局部作用域椭更,從而取代立即執(zhí)行函數(shù):

{  //用塊級作用域{}代替立即執(zhí)行函數(shù)
  let amount=5;
  function add(){
    amount+=1;
    console.log(amount);
    return amount;
  }
  function minus(){
    amount-=1;
    console.log(amount);
    return amount;
  }
  window.outer_add=add;  //暴露outer_add函數(shù),可間接訪問amount
  window.outer_minus=minus;  //暴露outer_minus函數(shù)蛾魄,可間接訪問amount
}

然后我們就可以操作outer_add和outer_minus了:

outer_add();   //結(jié)果為6
outer_add();   //結(jié)果為7
outer_minus();   //結(jié)果為6

塊級作用域的出現(xiàn)虑瀑,實際上使得獲得廣泛應(yīng)用的立即執(zhí)行函數(shù)表達式(IIFE)不再必要了。

這篇博客就先整理到這里滴须。由于個人水平有限缴川,博客錯誤之處,煩請指正描馅!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市而线,隨后出現(xiàn)的幾起案子铭污,更是在濱河造成了極大的恐慌恋日,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘹狞,死亡現(xiàn)場離奇詭異岂膳,居然都是意外死亡,警方通過查閱死者的電腦和手機磅网,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門谈截,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人涧偷,你說我怎么就攤上這事簸喂。” “怎么了燎潮?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵喻鳄,是天一觀的道長。 經(jīng)常有香客問我确封,道長除呵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任爪喘,我火速辦了婚禮颜曾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘秉剑。我一直安慰自己泛豪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布秃症。 她就那樣靜靜地躺著候址,像睡著了一般。 火紅的嫁衣襯著肌膚如雪种柑。 梳的紋絲不亂的頭發(fā)上岗仑,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機與錄音聚请,去河邊找鬼荠雕。 笑死,一個胖子當(dāng)著我的面吹牛驶赏,可吹牛的內(nèi)容都是我干的炸卑。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼煤傍,長吁一口氣:“原來是場噩夢啊……” “哼盖文!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚯姆,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤五续,失蹤者是張志新(化名)和其女友劉穎洒敏,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疙驾,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡凶伙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了它碎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片函荣。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖扳肛,靈堂內(nèi)的尸體忽然破棺而出傻挂,到底是詐尸還是另有隱情,我是刑警寧澤敞峭,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布踊谋,位于F島的核電站,受9級特大地震影響旋讹,放射性物質(zhì)發(fā)生泄漏殖蚕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一沉迹、第九天 我趴在偏房一處隱蔽的房頂上張望睦疫。 院中可真熱鬧,春花似錦鞭呕、人聲如沸蛤育。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓦糕。三九已至,卻和暖如春腋么,著一層夾襖步出監(jiān)牢的瞬間咕娄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工珊擂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留圣勒,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓摧扇,卻偏偏與公主長得像圣贸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子扛稽,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,440評論 2 348