我終於理解了閉包 本文寫於 2020 年 5 月 14 日 閉包這個詞一聽就很高級,令人害怕。 但實際上,閉包非常的強大,JS 的精髓之一就是閉包。 實際上,我們經常在使用閉包,而不自知! Kyle Simpson 在書中將掌握閉包,比喻為: 不像 Luke 一樣接受訓練才能掌握原力,而是像 Neo ...
我終於理解了閉包
本文寫於 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,完全不會有這種需要“泄露”的情況。
(完)