閉包無所不在,在你還沒知覺到的時候,你已經寫了一個閉包,像是這樣:
const outVar = "out variable";
const displayVar = function () {
console.log(outVar);
};
displayVar();
所以閉包在哪裡?閉包到底是什麼?
也許有人會說,騙人這哪有閉包,這就scope chain啊,沒錯!這是scope chain,但!也是閉包!(忍者說的,請參閱v2 p104)。
因為outVar和displayVar都在全域宣告,所以只要程式還在執行,scope就不會消失,看起來好像不是閉包,所以這也是為什麼要把閉包和他的小夥伴scope chain一起寫的原因。
像是這樣:
呼叫outer()時,會先執行黃色框內的code,其中定義了變數outerVar和函式innerFn(),並呼叫innerFn(),此時會執行紅色框,發現紅框內找不到outerVar,往外層有找到,可印出outerVar字串
const delayText = function () {
const text = "this is a delay message";
setTimeout(() => console.log(text), 2000);
};
delayText();
function countDogs() {
let dogNum = 0;
return function () {
dogNum++;
console.log(`${dogNum} dog(s)`);
};
}
const myFunc = countDogs();
myFunc(); //1 dog(s)
myFunc(); //2 dog(s)
myFunc(); //3 dog(s)
const outerFn = function () {
const outerVar = "I am from outer function";
const innerFn = function () {
console.log(outerVar);
};
return innerFn;
};
const myClosure = outerFn();
myClosure(); // I am from outer function
事情是這樣發生的:
我們把outerFn裡本來直接執行的innerFn改成傳出來,並存在另一個變數myClosure,當執行myClosure時,當初宣告innerFn的outerFn已經執行完,它的execution context早就消失了,但因為閉包的特性,innerFn還是可以從當初宣告的位置,依循scope chain找到需要的outerVar印出來
function pushFunc() {
var arr = [];
for (var i = 0; i < 3; i++) {
arr.push(function () {
console.log(i);
});
}
return arr;
}
var resultArr = pushFunc();
resultArr[0]();
resultArr[1]();
resultArr[2]();
function pushFunc() {
var arr = [];
for (let i = 0; i < 3; i++) {
arr.push(function () {
console.log(i);
});
}
return arr;
}
var resultArr = pushFunc();
resultArr[0]();
resultArr[1]();
resultArr[2]();
第一題答案是3 3 3,第二題答案是0 1 2。
主要差別在於for迴圈裡,變數i的宣告方式,第一題是用var;第二題是用let。
console.log(functionArr[0]);
//ƒ () {
// console.log(i);
// }
其實推幾次之後,好像會建立一種類直覺的感受,就可以比較快判斷出來了!tl;dr 閉包是一個包住函式定義與其scope chain的泡泡,它可以確保當函式執行時,無論當初的scope是否存在,都可以依循scope chain找到所需的變數。
ref
JS 原力覺醒 Day08 - Closures
所有的函式都是閉包:談 JS 中的作用域與 Closure