我終於理解了閉包


我終於理解了閉包

本文寫於 2020 年 5 月 14 日

閉包這個詞一聽就很高級,令人害怕。

但實際上,閉包非常的強大,JS 的精髓之一就是閉包。

實際上,我們經常在使用閉包,而不自知!

Kyle Simpson 在書中將掌握閉包,比喻為:

不像 Luke 一樣接受訓練才能掌握原力,而是像 Neo 見到矩陣一樣。

我們其實一直都在不自覺地使用着閉包,一旦理解了閉包,就會如同重生一般,鳳凰涅槃。

函數的作用域

function foo() {
  let hello = 'hello world'
  console.log(hello)
}

這個非常簡單的函數,如果調用foo(),那么在短暫的時間過后,hello 變量就會消失的無影無蹤。

仿佛從來沒有出現過一樣。

但是如果我們這么寫:

function foo() {
  let hello = 'hello world'
  function bar() {
    console.log(hello)
  }
  return bar
}

let hey = foo()

觀察一下,雖然在let hey = foo()的時候,foo 已經執行過了,但是如果我們使用hey(),依然可以使用 hello 變量!

這就是閉包。

foo()在執行之后,正常情況下,整個內部的作用域都會被銷毀,因為 JS 引擎會幫助我們自動回收垃圾。

而閉包神奇的可以阻止這件事情的發生,讓內部作用域依然存在,不被回收,讓bar()來使用。

bar()foo()執行結束后,依然保持對該作用域的引用,這就叫做閉包!

我們經常都在使用閉包

上面的代碼是不是十分眼熟?

function foo() {
  const hello = 'hello world'
  setInterval(function bar() {
    alert(hello)
  }, 1000)
}

這個定時器,是不是閉包?

定時器、事件監聽器、Ajax 請求、跨窗口通信……只要用到了回調函數,都是閉包!

以前 ES5 的時候,大家一直有個困惑:

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

為什么這個代碼每個一秒會輸出一次 5?

就算輸出也應該是 4 啊,為什么呢?

因為 var 的 i,作用域其實在外面。最后一次結束的時候,i 已經是 5 了。

而異步操作,都是在 for 循環結束之后才執行的。

也就是說,每次循環結束,都會記住setTimeout(() => { console.log(i) }, n000),然后在 for 結束之后,統一的把 i 傳入 console。

那自然而然的,會都輸出 5 了。

那這個怎么讓他每一次循環都能夠實現,console 對當前 i 的引用呢?這也是閉包呀。

把 setTimeout 單獨放到一個作用域里,然后再循環的時候把當前的 i 傳進去就可以了!非常簡單!

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

成功了!

但實際上,如果使用 let,完全不會有這種需要“泄露”的情況。

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM