Back to posts

JavaScript Scope Chain 與 Closure

Scope 到底是指哪裡?#

Scope 是變數的有效範圍。

function 裡面是 local,指的是在 local 這個範圍裡面有效。

function 外面是 global,指的是在 global 這個範圍裡面有效。

所以 Scope chain 是指?#

Scope chain 像是一張地圖。

Scope chain 生成在程式的創建階段,所以在執行之前就已經決定好 function 的作用域。換句話說,程式碼寫完就決定好作用域了。

如果使用了 function 外面宣告的變數,JavaScript 還是能依照 Scope chain 去 function 外面找到正確的變數來使用。如果外面還是找不到,就去外面的外面 … 直到找到它,找不到的話就拋出錯誤。

那 closure 呢?#

要理解 closure 要先知道作用域是已經決定好的了。

我們知道在 function 裡面的變數對 function 外面的作用域是沒有意義的,它會在呼叫 function 的時候創建,function 執行完之後釋放掉。所以不管我們呼叫幾次,function 裡面的變數都會重新建立。

舉個例子,在 function 裡面宣告一個變數 count,每次呼叫counter()count 的值都會從 0 開始。

function counter(){
  let count = 0;
  count = count + 1
  return count
}

console.log(counter()) // 1
console.log(counter()) // 1
console.log(counter()) // 1

我們也知道 function 裡面可以使用 function 外面的變數,因為該變數的作用域是 global 了,它不會隨著 function 執行完之後被釋放掉。

試著把 count 移到 global。每次呼叫 counter()count 的值都會累加。

let count = 0;
function counter(){
  count = count + 1
  return count
}

console.log(counter()) // 1
console.log(counter()) // 2
console.log(counter()) // 3

但如果不希望 count 的值暴露在 global,又希望 count 的值會累加,可以怎麼做?

用第一個例子,把 return count 的地方,用 return function(){} 包起來;再把 counter() 存成一個變數 myCounter

function counter(){
  let count = 0
  // 用 return function(){} 包起來
  return function(){
    count = count + 1
    return count
  }
}

// 再把 counter() 存成一個變數 myCounter
let myCounter = counter()

// 神奇的事情發生了
console.log(myCounter()); // 1
console.log(myCounter()); // 2
console.log(myCounter()); // 3

所以為什麼 count 可以累加了? 來看 let myCounter = counter() 這一行發生了什麼事。

  1. 執行 counter()
  2. function counter() 的作用域被產生了
  3. 回傳 function(){...}
  4. 存到了 myCounter

正常來說 function 執行完就釋放了,但是把 執行counter() 存入 myCounter 變數,讓 function counter 沒有被釋放掉,而是被保留下來了。所以後續我們執行 myCounter 的時候,就會讓 count 累加。

這個現象就叫做 closure。

參考#

閉包 重新介紹JavaScript function closures 重新認識 JavaScript: Day 19 閉包 Closure 所有的函式都是閉包:談 JS 中的作用域與 Closure