JS閉包

引子

我們先來看一個例子:

var n = 999;
function f1() {
  console.log(n);
}
f1() // 999

上面代碼中辉阶,函數(shù)f1可以讀取全局變量n练链。但是翔脱,函數(shù)外部無法讀取函數(shù)內(nèi)部聲明的變量。

function f1() {
  var n = 999;
}
console.log(n)
// Uncaught ReferenceError: n is not defined

上面代碼中兑宇,函數(shù)f1內(nèi)部聲明的變量n碍侦,函數(shù)外是無法讀取的。

如果有時需要得到函數(shù)內(nèi)的局部變量隶糕。正常情況下瓷产,這是辦不到的,只有通過變通方法才能實現(xiàn)枚驻。那就是在函數(shù)的內(nèi)部濒旦,再定義一個函數(shù)。

function f1() {
var n = 999;
  function f2() {
    console.log(n); // 999
  }
}

上面代碼中再登,函數(shù)f2就在函數(shù)f1內(nèi)部尔邓,這時f1內(nèi)部的所有局部變量,對f2都是可見的锉矢。既然f2可以讀取f1的局部變量梯嗽,那么只要把f2作為返回值,我們就可以在f1外部讀取它的內(nèi)部變量了沽损。

閉包是什么

我們可以對上面代碼進(jìn)行如下修改:

function f1() {
  var a = 999;
  function f2() {
    console.log(a);
  }
  return f2; // f1返回了f2的引用
}
var result = f1(); // result就是f2函數(shù)了
result();  // 執(zhí)行result灯节,全局作用域下沒有a的定義,
//但是函數(shù)閉包,能夠把定義函數(shù)的時候的作用域一起記住炎疆,輸出999       

上面代碼中卡骂,函數(shù)f1的返回值就是函數(shù)f2,由于f2可以讀取f1的內(nèi)部變量形入,所以就可以在外部獲得f1的內(nèi)部變量了全跨。

閉包就是函數(shù)f2,即能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)亿遂。由于在JavaScript語言中浓若,只有函數(shù)內(nèi)部的子函數(shù)才能讀取內(nèi)部變量,因此可以把閉包簡單理解成“定義在一個函數(shù)內(nèi)部的函數(shù)”崩掘。閉包最大的特點七嫌,就是它可以“記住”誕生的環(huán)境,比如f2記住了它誕生的環(huán)境f1苞慢,所以從f2可以得到f1的內(nèi)部變量诵原。在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁挽放。

那到底什么是閉包呢绍赛?
當(dāng)一個函數(shù)能夠記住并訪問到其所在的詞法作用域及作用域鏈,特別強調(diào)是在其定義的作用域外進(jìn)行的訪問辑畦,此時該函數(shù)和其上層執(zhí)行上下文共同構(gòu)成閉包吗蚌。

閉包就是函數(shù)中的函數(shù),里面的函數(shù)可以訪問外面函數(shù)的變量纯出,外面的變量的是這個內(nèi)部函數(shù)的一部分蚯妇。

閉包形成的條件

  • 函數(shù)嵌套
  • 內(nèi)部函數(shù)引用外部函數(shù)的局部變量

閉包的特性

每個函數(shù)都是閉包,每個函數(shù)天生都能夠記憶自己定義時所處的作用域環(huán)境暂筝。把一個函數(shù)從它定義的那個作用域箩言,挪走,運行焕襟。這個函數(shù)能夠記憶住定義時的那個作用域陨收。不管函數(shù)走到哪里,定義時的作用域就帶到了哪里鸵赖。

//例題1
var inner;
function outer() {
  var a=250;
  inner=function() {
    alert(a);//這個函數(shù)雖然在外面執(zhí)行务漩,但能夠記憶住定義時的那個作用域,a是250
  }
}
outer();
var a=300;
inner();//一個函數(shù)在執(zhí)行的時候它褪,找閉包里面的變量饵骨,不會理會當(dāng)前作用域。
//例題2
function outer(x) {
  function inner(y) {
    console.log(x+y);
  }
  return inner;
}
var inn = outer(3); // 數(shù)字3傳入outer函數(shù)后茫打,inner函數(shù)中x便會記住這個值
inn(5); // 當(dāng)inner函數(shù)再傳入5的時候宏悦,只會對y賦值镐确,所以最后彈出8

閉包的內(nèi)存泄漏

棧內(nèi)存提供一個執(zhí)行環(huán)境包吝,即作用域饼煞,包括全局作用域和私有作用域,那他們什么時候釋放內(nèi)存的诗越?

  • 全局作用域----只有當(dāng)頁面關(guān)閉的時候全局作用域才會銷毀
  • 私有的作用域----只有函數(shù)執(zhí)行才會產(chǎn)生

一般情況下砖瞧,函數(shù)執(zhí)行會形成一個新的私有的作用域,當(dāng)私有作用域中的代碼執(zhí)行完成后嚷狞,我們當(dāng)前作用域都會主動的進(jìn)行釋放和銷毀块促。但當(dāng)遇到函數(shù)執(zhí)行返回了一個引用數(shù)據(jù)類型的值,并且在函數(shù)的外面被一個其他的東西給接收了床未,這種情況下一般形成的私有作用域都不會銷毀竭翠。

如下面這種情況:

function fn() {
  var num=100;
  return function() {}
}
var f = fn(); // fn執(zhí)行形成的這個私有的作用域就不能再銷毀了

也就是像上面這段代碼,fn函數(shù)內(nèi)部的私有作用域會被一直占用的薇搁,發(fā)生了內(nèi)存泄漏斋扰。所謂內(nèi)存泄漏指任何對象在您不再擁有或需要它之后仍然存在。閉包不能濫用啃洋,否則會導(dǎo)致內(nèi)存泄露传货,影響網(wǎng)頁的性能。閉包使用完后宏娄,要立即釋放資源问裕,將引用變量指向null

接下來我們看下有關(guān)于內(nèi)存泄漏的一道經(jīng)典面試題:

function outer() {
  var num=0; // 內(nèi)部變量
  return function add() { // 通過return返回add函數(shù)孵坚,就可以在outer函數(shù)外訪問了
    num++;//內(nèi)部函數(shù)有引用粮宛,作為add函數(shù)的一部分了
    console.log(num);
  };
 }
var func1 = outer();
func1(); // 實際上是調(diào)用add函數(shù), 輸出1
func1(); // 輸出2 因為outer函數(shù)內(nèi)部的私有作用域會一直被占用
var func2 = outer();
func2(); // 輸出1  每次重新引用函數(shù)的時候卖宠,閉包是全新的巍杈。
func2(); // 輸出2  

閉包的作用

  1. 可以讀取函數(shù)內(nèi)部的變量
  2. 可以使變量的值長期保存在內(nèi)存中,生命周期比較長逗堵。因此不能濫用閉包秉氧,否則會造成網(wǎng)頁的性能問題
  3. 可以用來實現(xiàn)JS模塊

JS模塊:具有特定功能的js文件,將所有的數(shù)據(jù)和功能都封裝在一個函數(shù)內(nèi)部(私有的),只向外暴露一個包信n個方法的對象或函數(shù)蜒秤,模塊的使用者汁咏,只需要通過模塊暴露的對象調(diào)用方法來實現(xiàn)對應(yīng)的功能。

//index.html文件
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
  myModule2.doSomething()
  myModule2.doOtherthing()
</script>
//myModule.js文件
(function () {
  var msg = 'Beijing'//私有數(shù)據(jù)
  //操作數(shù)據(jù)的函數(shù)
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }
  //向外暴露對象(給外部使用的兩個方法)
  window.myModule2 = {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
})()

閉包的運用

我們要實現(xiàn)這樣的一個需求: 點擊某個按鈕作媚,提示"點擊的是第n個按鈕"攘滩,此處我們先不用事件代理:

.....
<button>測試1</button>
<button>測試2</button>
<button>測試3</button>
<script type="text/javascript">
  var btns = document.getElementsByTagName('button')
    for (var i = 0; i < btns.length; i++) {
      btns[i].onclick = function () {
        console.log('第' + (i + 1) + '個')
      }
     }
</script>  

點擊任意一個按鈕,后臺都是彈出“第四個”纸泡,這是因為i是全局變量,執(zhí)行到點擊事件時漂问,此時i的值為3。那該如何修改,最簡單的是用let聲明i蚤假。

for (let i = 0; i < btns.length; i++) {
  btns[i].onclick = function() {
    console.log('第' + (i + 1) + '個')
  }
}

另外我們可以通過閉包的方式來修改:

for (var i = 0; i < btns.length; i++) {
  (function (j) {
    btns[j].onclick = function () {
      console.log('第' + (j + 1) + '個')
    }
  })(i)
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末栏饮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子磷仰,更是在濱河造成了極大的恐慌袍嬉,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灶平,死亡現(xiàn)場離奇詭異伺通,居然都是意外死亡,警方通過查閱死者的電腦和手機逢享,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門罐监,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瞒爬,你說我怎么就攤上這事弓柱。” “怎么了疮鲫?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵吆你,是天一觀的道長。 經(jīng)常有香客問我俊犯,道長妇多,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任燕侠,我火速辦了婚禮者祖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绢彤。我一直安慰自己七问,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布茫舶。 她就那樣靜靜地躺著械巡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饶氏。 梳的紋絲不亂的頭發(fā)上讥耗,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機與錄音疹启,去河邊找鬼古程。 笑死,一個胖子當(dāng)著我的面吹牛喊崖,可吹牛的內(nèi)容都是我干的挣磨。 我是一名探鬼主播雇逞,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼茁裙!你這毒婦竟也來了塘砸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤呜达,失蹤者是張志新(化名)和其女友劉穎谣蠢,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體查近,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年挤忙,在試婚紗的時候發(fā)現(xiàn)自己被綠了霜威。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡册烈,死狀恐怖戈泼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赏僧,我是刑警寧澤大猛,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站淀零,受9級特大地震影響挽绩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜驾中,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一唉堪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肩民,春花似錦唠亚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至工窍,卻和暖如春割卖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背移剪。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工究珊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纵苛。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓剿涮,卻偏偏與公主長得像言津,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子取试,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,647評論 2 354

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

  • 閉包(closure)是Javascript語言的一個難點悬槽,也是它的特色,很多高級應(yīng)用都要依靠閉包實現(xiàn)瞬浓。 一初婆、變量...
    zock閱讀 1,075評論 2 6
  • 一、變量的作用域 要懂得閉包猿棉,起首必須懂得Javascript特別的變量作用域磅叛。 變量的作用域無非就是兩種:全局變...
    杭州程序員小陳閱讀 212評論 0 0
  • 閉包的概念 閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。 一萨赁、變量的作用域 要理解閉包弊琴,首先必須理解Javascrip...
    遇而記起閱讀 323評論 0 0
  • 一、變量的作用域 要理解閉包杖爽,首先必須理解Javascript特殊的變量作用域敲董。 變量的作用域無非就是兩種:全局變...
    紫陌蘭溪閱讀 287評論 0 4
  • 媽媽化焕,我對不起你萄窜! 看見你滿身的傷痕,我無能為力锣杂! 媽媽脂倦,我對不起你! 你讓我好好學(xué)習(xí)元莫,我卻留戀人間 媽媽赖阻,我對不...
    曖雪兒閱讀 715評論 0 0