Yu Ken Code

[筆記]為何 Closure 在 JavaScript 中會這麼重要?

2021/10/21js3733 minutes

[筆記]為何 Closure 在 JavaScript 中會這麼重要?

目錄

  • 關於全域變數的優點與缺點
  • 語彙範疇(Lexical Scope)的應用
  • Closure 讓內部變數被記住
  • React Hook 中 Closure 無所不在
  • 結論

Closure 是一個光看名詞很難理解他意思的一個概念,一直以來看了很多文章也跟許多人討論,才慢慢理解他的概念及應用

我認為要理解 Closure,需要從一個情境說起

關於全域變數的優點與缺點

假設我們今天有個情境是需要有一個計數器,需要計算今天進來我們這家店的人有多少

let peopleCount = 0;

const counter = () => {
  peopleCount++;
};

counter(); // 每走進來一個人,就呼叫一次

console.log(peopleCount); // 1

如果我們在外面不小心改到 peopleCount

let peopleCount = 0;

const counter = () => {
  peopleCount++;
};

counter(); // 每走進來一個人,就呼叫一次

peopleCount = 1000;

console.log(peopleCount); // 1000

之前辛苦計算的人數就全毀了,除此之外,往後如果還需要計算進來的寵物、女性、男性或是老人數量,那全域變數的數量會越來越龐大,雖然可以用物件把它全部包起來,但還是難以逃脫不小心被改到的風險

語彙範疇(Lexical Scope)的應用

語彙範疇可以解決上述不小心改到全域變數的風險,基本上語彙範疇就是每個區塊程式碼能作用的區域

const name = "Ken";

function getName() {
  const name = "John";
  const age = 25;

  console.log(name);
}

getName(); // "John"
console.log(age); // "age is not defined"

getName 函式的 name 會優先存取到 { } 內部的 name,所以是 "John",若裡面存取不到,就會往外存取,進而找到外部的 "Ken"

然而,當我們要存取 age 這個變數時,卻說 age is not defined,因為外部沒有辦法存取到內部的變數,也因為這樣我們可以用這招避免外部去改動到 age 這個變數

所以回到剛剛算人進來的例子

const counter = () => {
  let peopleCount = 0;

  peopleCount++;

  console.log(peopleCount);
};

counter(); // 1
counter(); // 1
counter(); // 1

這樣就不用擔心會改到 peopleCount 這個變數

但問題來了,不管呼叫幾次,counter 都是 1

原因是每次呼叫 peopleCount 這個變數都會是從零開始,他並不會累加

Closure 讓內部變數被記住

const counter = () => {
  let peopleCount = 0;

  const addPerson = () => peopleCount++;

  return addPerson;
};

const amount = counter();

console.log(amount()); // 0
console.log(amount()); // 1
console.log(amount()); // 2

在我們呼叫 counter 的時候,由於 addPerson 這個函式裡面有存取到 peopleCount 這個變數,所以 counter 會幫我們保留這個內部的變數

之後我們在呼叫 counter 裡面的 addPerson 這個函式的時候,就可以把 peopleCount 累加上去了

我們可以稍微改寫成立即呼叫的函式表示式(IIFE)

const useCounter = (() => {
  const counter = () => {
    let peopleCount = 0;

    const addPerson = () => peopleCount++;

    return addPerson;
  };
  return counter;
})();

const amount = useCounter();

console.log(amount()); // 0
console.log(amount()); // 1
console.log(amount()); // 2

React Hook 中 Closure 無所不在

其實在許多框架或是套件裡,也有許多 Closure 的應用,舉我們熟悉的 React Hook 為例

今天要製作一個 custom hook

const useState = initValue => {
  let _value = initValue;
  const state = () => _value;

  const setState = newValue => {
    _value = newValue;
  };

  return [state, setState];
};

用解構陣列,最終回傳兩個 function,第一個值,第二個是改變這個值的方法

接著我們開始使用這個 hook

const [counter, setCounter] = useState(1);

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

setCounter(2);

console.log(counter()); // 2

第一個 console 結果:

一開始 initValue 傳進去之後,賦值給 _value 後,由於 state 這個 function 有存取到 _value 這個變數,所以 useState 幫我們保留了這變數值,下次呼叫 counter 這個方法的時候,不需要傳入 initValue 這個參數,也能得到 1 這數值

第二個 console 結果:

同理,改變後的 _value 也同樣被存在 useState 這個 function 中,下次呼叫 counter 這個方法的時候,不需要傳入 initValue 這個參數,也能得到 2 這數值

結論

  • 語彙範疇解決全域變數容易被更改到的問題
  • Closure 讓每個語彙範疇內能保存有被存取到的變數
  • 再搭配 IIFE 能讓每個方法簡潔地獨立切割出來,包含裡面的方法和各種變數

tags:closure

date:2021/10/21