因為這幾天剛好朋友來公司面試,我們討論著一些前端面試必考題,想說也工作半年了,可以來恢復一下失去的記憶…
閉包
啊!變數根據作用域的不同分為兩種:全域性變數和區域性變數。
**「作用域」一詞,指的正是作用域環境在程式碼指定變數時,使用 location 來決定該變數用在哪裡的事情。
function randomString(length) {
var result = ''
var characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
var charactersLength = characters.length
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
var demoData = [] //全域變數
function getData() {
var demoData = [] //區域變數
for (let i = 0; i < 1000; i++) {
demoData.push(randomString(1000))
}
}
getData()
閉包的出現
—>延伸變數作用域範圍,讀取函式內部的變數
—>讓這些變數的值始終保持在記憶體中不被釋放,讓外部可以執行。
影響 —> 讓 function 可以重複被使用
做法 (Scopes nesting)
—> 在子函式部分使用父函式所宣告的變數,就可避免父函式所宣告的變數在沒有參照的情況下被釋放掉。儲存內層 Function 變數不被釋放,重複使用。
function storeMoney() {
// 宣告父函數
var money = 1000 // 外層 money 此時的變數可以被內層的 function 存取
return function (price) {
// (閉包)子函數引用父函數
money = money + price // 不斷取用外層所宣告的變數(範圍鍊)
return money // 內層的 money 後來是私有的變數,外層無法讀取
}
}
console.log(storeMoney()) // 返回子函數
//1. 直接呼叫內層函式
console.log(storeMoney()(100)) // 1100 (存取內部函式的變數)
//呼叫一次外層,再呼叫一次內層,price的值為100
//2.外層函式賦予到另一個變數上
var debbyMoney = storeMoney()
console.log(debbyMoney)
//storMoney是一個表達式,所以會回傳一段函式,建立一個變數(debbyMoney)儲存,如此storeMoney的記憶體就不會被釋放
console.log(debbyMoney(1000)) //2000
console.log(debbyMoney(1000)) //3000
console.log(debbyMoney(1000)) //4000
//3. function重複使用
var riverMoney = storeMoney()
console.log(riverMoney)
//storMoney是一個表達式,所以會回傳一段函式,建立一個變數(riverMoney)儲存,如此storeMoney的記憶體就不會被釋放
console.log(riverMoney(1)) //2000
console.log(riverMoney(10)) //3000
console.log(riverMoney(100)) //4000
//他們共享函式的定義,卻保有不同的環境
QA1
var msg = "global."
function outer() {
var msg = "local."
function inner() {
return msg;
}
return inner;
}
var innerFunc = outer();
var result = innerFunc();
console.log( result ); // ?
//程式碼在撰寫完成的同時,就已經先確立了作用域,運行的過程中都不會改變其作用域
QA: 我想要每過一秒印出 1 2 3 4 5
for (var i = 0; i < 3; i++) {
setTimeout(function log() {
console.log(i) // What is logged?
}, 1000)
console.log('var i', i)
}
console.log('var i', i)
// var i = 0 的作用域範圍是 function,for 在1000毫秒前就跑了三次,最後一次 i 停留在3
// 這時內層的 log()要執行console.log(i),變數 i 會往作用域的外層去尋找,就找到上面迴圈的那個變數 i,也就是 3
for (let i = 0; i < 3; i++) {
setTimeout(function log() {
console.log(i) // What is logged?
}, 1000)
console.log('let i', i)
}
console.log('let i', i) //i is not defined
有閉包跟無閉包的差異
var count = 0;
function counter(){
return ++count;
}
console.log( counter() ); // 1
console.log( counter() ); // 2
console.log( counter() ); // 3
function counter(){
var count = 0;
return function(){
return ++count;
}
}
var countFunc = counter();
console.log( countFunc() ); // 1
console.log( countFunc() ); // 2
console.log( countFunc() ); // 3
//甚至可以使用同一個方向再次複製出獨立的環境
var countFunc2 = counter();
console.log( countFunc2() ); // 1
console.log( countFunc2() ); // 2
透過閉包,可以同時執行多種方法
function storeMoney(initValue) {
var money = initValue || 1000 // 若 initValue 有值就套用,或給 1000
return {
// 使用 return 回傳一個物件,每個物件裡都是一段函式
increase: function (price) {
money += price
},
decrease: function (price) {
money -= price
},
value: function () {
return money
},
}
}
var MingMoney = storeMoney(100)
MingMoney.increase(100)
MingMoney.increase(100)
MingMoney.decrease(25)
MingMoney.decrease(96)
console.log(MingMoney.value()) // 179
var timMoney = storeMoney(1000)
timMoney.increase(1000)
console.log(timMoney.value()) // 2000
你已經用過的 JS closure
let countClicked = 0;
myButton.addEventListener('click', function handleClick() {
countClicked++;
myText.innerText = `You clicked ${countClicked} times`;
});
const message = 'Hello, World!';
setTimeout(function callback() {
console.log(message); // logs "Hello, World!"
}, 1000);
總結
為何需要閉包
明天就要結賽了,心情也真是有種高興但又有種壓力的感覺,即使到現在每天都還是一堆聽不懂的東西,學不完的東西,牙一咬還是要繼續走下去呢!