Javascript閉包理解

何為閉包?

閉包(Closure)是一個封閉的作用域,它可以訪問外部作用域的變量。

說起來比較抽象,實際上閉包就是一個函數(shù),函數(shù)內(nèi)部可以訪問外部的變量,比如下面這個例子:

function sayHello() {
  const greet = 'hello world';
  function helloFunc() {
    console.log(greet);
  }
  helloFunc();
}
sayHello(); // hello world

helloFunc函數(shù)內(nèi)雖然沒有定義greet變量,但是它的外層函數(shù)sayHello函數(shù)里定義了greet變量,所以最后成功輸出hello world,這個例子里面helloFunc就是一個閉包,尋找變量的過程是按照作用域鏈來尋找的。

閉包的作用

我們知道javascript外部作用域無法訪問內(nèi)部作用的值。

function func() {
  var name = 'jack';
}
console.log(name);  // undefined

但是函數(shù)內(nèi)部卻可以訪問外部變量。

var name = 'jack';
function func() {
  console.log(name);
}
func(); // jack

所以函數(shù)內(nèi)部可以修改外部狀態(tài),我們可以讓函數(shù)擁有狀態(tài),比如迭代器生成器。

var add = (function() {
  var counter = 0;
  return function() {
    counter++;
    return counter;
  };
})();
add(); // 1
add(); // 2
add(); // 3

這個例子我們使用了一個立即執(zhí)行函數(shù)表達式(IIFE)創(chuàng)建了一個匿名函數(shù)也就是一個閉包,函數(shù)返回的函數(shù)會引用這個匿名函數(shù)的局部變量counter,所以我們每次調(diào)用add函數(shù)輸出的值都不一樣,add函數(shù)因為閉包有了狀態(tài)。

這里使用立即執(zhí)行函數(shù)主要是為了直接返回最終函數(shù),也可以返回個普通函數(shù)像下面這樣:

var genAddFunc = function() {
  var counter = 0;
  return function() {
    counter++;
    return counter;
  }
}
var add = genAddFunc();
add(); // 1
add(); // 2
add(); // 3
閉包的應(yīng)用

比如我們需要對多個li綁定點擊事件如下:

<html>
  <body>
    <ul id="itemList">
      <li>item 1</li>
      <li>item 2</li>
      <li>item 3</li>
    </ul>
  </body>
</html>
const itemList = document.getElementById('itemList');
const items = itemList.getElementsByTagName('li');
for (var i = 0; i < items.length; i++) {
  items[i].onclick = function() {
    console.log(`item ${i} clicked`);
  }
}
items[0].click(); // item 3 clicked
items[1].click(); // item 3 clicked
items[2].click(); // item 3 clicked

我們發(fā)現(xiàn)雖然li的點擊事件都已經(jīng)創(chuàng)建了閉包記住了i的值,但是都是輸出3而不是0,1,2這是為什么呢?
原因是for循環(huán)中大括號包含該的部分并不是一個封閉的作用域,通過下面代碼可以驗證:

for (var i = 0; i < 3; i++) {}
console.log(i); // 3
console.log(window.i); // 3

當for循環(huán)結(jié)束后變量i并沒有被回收,實際上我們是創(chuàng)建了一個全局變量i,3個item的點擊事件函數(shù)綁定的是全局變量i的值,所以最后都輸出的是item 3 clicked,通過下面代碼可以驗證:

for (var i = 0; i < items.length; i++) {
  items[i].onclick = function() {
    console.log(`item ${i} clicked`);
  }
}
i = 99;
items[0].click(); // item 99 clicked
items[1].click(); // item 99 clicked
items[2].click(); // item 99 clicked

當我們修改了i的值發(fā)現(xiàn)3個點擊事件的結(jié)果也都變成了item 99 clicked,說明它們輸出的i綁定的都是最新i的值而不是當時循環(huán)時i的值

于是我們嘗試一下使用一個局部變量看看能否保存i的值,這樣我們就可以輸出正確的i的值:

for (var i = 0; i < items.length; i++) {
  items[i].onclick = function() {
    var j = i;
    console.log(`item ${j} clicked`);
  }
}
i = 99;
items[0].click(); // item 99 clicked
items[1].click(); // item 99 clicked
items[2].click(); // item 99 clicked

使用局部變量j保存i還是沒有成功,我們修改了i的值后j的值也發(fā)生了變化,說明js在執(zhí)行過程中j還是引用i的值。

解決方法1:函數(shù)傳參

for (var i = 0; i < items.length; i++) {
  items[i].onclick = function() {
    console.log(`item ${j} clicked`);
  } (i)
}
i = 99;
items[0].click(); // item 3 clicked
items[1].click(); // item 3 clicked
items[2].click(); // item 3 clicked

通過傳遞參數(shù)我們得到了正確的結(jié)果,修改了i的值也沒有影響點擊事件的結(jié)果。

解決方法2: 使用閉包

for (var i = 0; i < items.length; i++) {
  items[i].onclick = (function() {
    var j = i;
    return function() {
      console.log(`item ${j} clicked`);
    }
  })();
}
i = 99;
items[0].click(); // item 3 clicked
items[1].click(); // item 3 clicked
items[2].click(); // item 3 clicked

在閉包中我們用j來保存i的值,這樣我們就能記住當時循環(huán)時i的值了,和之前的單獨一個function區(qū)別是閉包可產(chǎn)生獨立的作用域這樣j就是i的值的拷貝而不是i的引用,我是這么理解的。

解決方法2: 使用let
使用let應(yīng)該是最優(yōu)雅的解決辦法了,let是ES6的關(guān)鍵字它能夠產(chǎn)生封閉的作用域:

for (let i = 0; i < items.length; i++) {
  items[i].onclick = function() {
    console.log(`item ${i} clicked`);
  }
}
items[0].click(); // item 3 clicked
items[1].click(); // item 3 clicked
items[2].click(); // item 3 clicked
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,535評論 0 13
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學(xué)習記錄文檔,今天18年5月份再次想寫文章,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 3,123評論 2 9
  • --本內(nèi)容來自吳曉波音頻--就在微博即將在微信朋友圈的圍攻下,失去市場的時候,微博做對了以下兩件事,而起死回生:1...
    劉書亞的天堂之路閱讀 180評論 0 0
  • 近幾年互聯(lián)網(wǎng)購買保險火的一匹,之前雖有各家網(wǎng)絡(luò)平臺在出售保險產(chǎn)品,但真正把互聯(lián)網(wǎng)保險推到家家戶戶的,不得不說支付寶...
    他她保閱讀 5,422評論 0 3
  • 001目標是一種導(dǎo)向。比如,當忙完手頭上的事情迷茫接下來要干什么的時候想一想自己的目標,則知道應(yīng)該要去做什么了,不...
    4d41bb58f6ff閱讀 178評論 0 0

友情鏈接更多精彩內(nèi)容