本文很多理論基礎以及概念來自於《你不知道的JavaScript》,感謝KYLE SIMPSON先生。首先要說明,閉包是和作用域息息相關的。
function foo(){
var a = 2;
function bar(){console.log(a)}
return bar;
}
var baz = foo();
baz(); //2
這段代碼,函數bar()被當做一個值類型進行傳遞,這個時候就產生了閉包。這裏呢,bar()聲明的位置來看,他擁有訪問foo()內部作用域的閉包。也就是bar()對該作用域的引用。強調一下概念,(訪問自身作用域【函數或塊作用域】)的函數被傳遞,就會產生閉包在這些函數中的應用。
通俗點說,這個函數能夠讀取到其他函數的內部變量,並且在這個函數的詞法作用域之外被執行了。
下面概念清晰了,讓我們來接觸一道經典的閉包面試題:
for (var i = 0; i < 5; ++i) {
setTimeout(function () {
console.log(i + ' ');
}, 100);
}
屁話不多說,執行結果五個六。背景知識:setTimeout註冊的函數需要等待線程空閒才能執行,也就是for循環執行之後。
所以即使是setTimeout(..,0)輸出結果也不會變。現在的問題就是,當函數執行的時候,訪問的都是同一個i,所以我們要將函數自己圈起來一個閉包。
for (var i = 0; i < 5; ++i) {
(function () {
setTimeout(function () {
console.log(i + ' ');
}, 100);
}());
}
這樣,通過這個IIFE函數確實包起來一個作用域,但是這個作用域卻沒有什麼內容,那我們給他加點內容進去。
for (var i = 0; i < 5; ++i) {
(function (i) {
setTimeout(function () {
console.log(i + ' ');
}, 100);
}(i));
}
加了個i,這樣內存中就會有五個i,分別存在於函數的作用域中。es6語法推出了塊作用域,還可以通過將var 改成let來操作。
for (let i = 0; i < 5; ++i) {
setTimeout(function () {
console.log(i + ' ');
}, 100);
}
let在for循環中,每次循環都會聲明一次,for循環了5次,在每個函數作用域中都存在一個自己的i.