在認識Closure之前,需要先了解什麼是Scope

Scope

Lexical Scope

中文可以解讀成語彙範疇,會將字串的Token解析成好幾段,例如:

1
let count = 0;

解析成let, count, =, 0, ;
試問以下的Scope有幾層?

1
2
3
4
5
6
7
8
9
10
11
function foo(a) {
let b = a + 1;

function bar(c) {
console.log(a+c);
}

bar(b);
}

foo(0);

有答案了嗎?是三個。
這裡還要補充幾個知識

  1. Shadowing

    • 如果內層有宣告外層相同的名稱,則會採用內層的,且會採用第一個找到的。可參考以下的範例
      1
      2
      3
      4
      5
      6
      7
      8
      var currencySymbol = "$";

      function showMoney(amount) {
      var currencySymbol = "€";
      document.write(currencySymbol + amount);
      }

      showMoney("100");​ // €100
  2. Global variable

    • 全域變數會自動變成全域物件的屬性,因此如果在巢狀內要呼叫相同名字的Global,可以呼叫window.xxx

Dynamic Scope

查看以下程式,你會發現無法在foo中取得a的值,因此會取得global上的。

1
2
3
4
5
6
7
8
9
10
11
function foo() {
console.log(a); // 3
}

function bar() {
var a = 2;
foo();
}

var a = 3;
bar();

Dynamic Scope在runtime的時候就決定了範疇,其實只要運用call stack的邏輯去分析,大概就能知道原理了。
但是JavaScript是沒有Dynamic Scope的,運用this達到類似的效果。

Closure

知道Scope後,這邊就好解釋了。
Closure簡單來說就是,具有Scope的reference,使在範疇之外呼叫也能正常運行。

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
var a = 5;

function bar() {
console.log(a);
}

return bar;
}

var bee = foo();

bee();

ES6

在ES6過後,可將一個檔案視為一個模組。可將檔案匯入或匯出成API之一。
tw.js

1
2
3
4
5
function kf(money) {
return `Let fadacai: ${money}`;
}

export kf;

room.js

1
2
3
4
5
6
7
8
9
10
11
import kf from 'tw'; // 只會載入 tw module 的 kf

let sleep = 'sleep';

function meeting() {
console.log(
kf(sleep).toUpperCase()
);
}

export meeting;

summary.js

1
2
3
4
5
6
7
8
9
// 載入 foo 與 bar 的完整模組
import tw from 'tw';
module room from 'room';

console.log(
kf.fadacai(0)
);

room.meeting();

不需要擔心順序, 命名等問題,想放什麼就放什麼。