其實 Closure 隨處可見

Closure 是 ECMAScript 的代表性功能,也是 Functional Programming 的基礎,很多神妙的 FP 機制都是由 Closure 展開,善用 Closure 將使得程式碼更為精簡,可讀性更高,也更容易維護。

Version

ECMAScript 2015

Function Factory


藉由 function 的 parameter 建立新的 function,而新的 function 則藉由 Closure 讀取原 function 的 parameter

Higher Order Function

1
2
3
4
const data = [1, 2, 3];

console.log(data.map(x => x * 2));
console.log(data.map(x => x * 2 + 1));

ECMAScript 提供很多 Higher Order Function,如 Array.prototypesetTimeout() ,我們會傳入 Arrow Function。

若 Arrow Function 有重複,或者類似規則的 Arrow Function 不斷出現,則可藉由 Closure 抽出 Function Factory。

1
2
3
4
5
6
const data = [1, 2, 3];

const makeMapper = (a, b) => x => a * x + b;

console.log(data.map(makeMapper(2, 0)));
console.log(data.map(makeMapper(2, 1)));

抽出 makeMappr(),藉由傳入不同 argument 給 makeMapper() 產生不同的 Arrow Fuction 給 map()

closure000

Object Method

1
2
3
4
5
6
7
const foo = {
func1: x => 2 * x + 3,
func2: x => 3 * x + 1,
};

console.log(foo.func1(1));
console.log(foo.func2(1));

當使用 Object Literal 定義 method 時,若 method 有重複,或者類似規則的 method 不斷出現,則可藉由 Closure 抽出 Function Factory。

1
2
3
4
5
6
7
8
9
const makeFunc = (a, b) => x => a * x + b;

const foo = {
func1: makeFunc(2, 3),
func2: makeFunc(3, 1),
};

console.log(foo.func1(1));
console.log(foo.func2(1));

func1()func2() 可藉由 makeFunc() 產生。

抽出 makeFunc(),藉由傳入不同 argument 給 makeFunc() 產生不同的 method。

closure001

Factory Function 是是由 function 建立 object;而 Function Factory 是由 function 建立 function

Partial Application


Function 若只傳部分 argument 進去,則會回傳新的 function;直到所有 argument 都傳遞完全才會開始求值

1
2
3
4
5
const func = (x, y, z) => 2 * x + 3 * y + z;

console.log(func(10, 3, 2));
console.log(func(10, 1, 2));
console.log(func(10, 4, 2));

func 為多 argument 的 function,但實務上發現第 1 個 argument 都一樣,只有第 2 個與第 3 個 argument 在改變。

1
2
3
4
5
6
7
const func = x => (y, z) => 2 * x + 3 * y + z;

const func1 = func(10);

console.log(func1(3, 2));
console.log(func1(1, 2));
console.log(func1(4, 2));

將第 1 個 argument 傳入 func() 即可,會傳回新的 func1()

然後只需將第 2 個與第 3個 argument 傳入 func1() 即可得到結果,不需再重複傳入第一個 argument。

closure002

Currying


將原本 n 個 argument 的 function 改用 n 個 1 個 argument 的 function 所構成

1
2
3
4
5
6
7
const func = (x, y, z) => 2 * x + 3 * y + z;

console.log(func(1, 1, 1));
console.log(func(1, 1, 2));
console.log(func(1, 2, 2));
console.log(func(1, 2, 3));
console.log(func(1, 3, 4));

Partial Application 固然靈活,但只能針對特定 argument 組合回傳 function,若所有 funtion 都只有 1 個 argument,則 argument 的排列組合是最靈活的。

1
2
3
4
5
6
7
8
9
10
const func = x => y => z => 2 * x + 3 * y + z;

const func1 = func(1)(1);
const func2 = func(1)(2);

console.log(func1(1));
console.log(func1(2));
console.log(func2(2));
console.log(func2(3));
console.log(func(1)(3)(4));

無論需求怎麼變,都能任意湊出想要的 function。

closure003

Encapsulation


1
2
3
4
5
6
7
8
const foo = {
name: 'Sam',
sayHello: function() {
return `Hello ${this.name}`;
},
};

console.log(foo.sayHello());

使用 Object Literal 建立 object,所有的 property 都是 public 的,完全沒有任何 Encapsulation 可言,該如何如 OOP 有 private field 呢 ?

1
2
3
4
5
6
7
const foo = (function(name) {
return {
sayHello: () => `Hello ${name}`,
}
}('Sam'));

console.log(foo.sayHello());

使用 Closure + IIFE,name 可如 OOP 的 constructor 般傳入設定初始值,重點是 name 被封裝在內部,外界無法更改。

closure005

1
2
3
4
5
6
7
8
9
10
const foo = (function(name) {
const addTitle = name => `Mr. ${name}`;
const title = addTitle(name);

return {
sayHello: () => `Hello ${title}`,
}
}('Sam'));

console.log(foo.sayHello());

也能在 Anonymous Function 增加 variable 與 function,相當於 OOP 的 private field 與 private method。

closure004

Memorization


將耗時的運算結果存起來,若第二次執行該 function,則直接從 cache 讀出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const isPrime = value => {
if (!isPrime.cache) isPrime.cache = {};
if (isPrime.cache[value]) return isPrime.cache[value];

let prime = value !== 1;
for(let i = 2; i < value; i++) {
if (value % 2 === 0) {
prime = false;
break;
}
}

return isPrime.cache[value] = prime;
};

console.log(isPrime(13));

判斷質數 是很耗時的運算,我們希望能將運算過的結果 cache 下來,可直接對 isPrime() 新增 cache property,若 cache 有值則直接回傳,若 cache 無值才運算。

closure006

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const isPrime = (() => {
let cache = {};

return value => {
if (cache[value]) return cache[value];

let prime = value !== 1;
for(let i = 2; i < value; i++) {
if (value % 2 === 0) {
prime = false;
break;
}
}

return cache[value] = prime;
};
})();

console.log(isPrime(13));

Memorization 另一種實現方式是利用 Closure,這種方式的優點在於 Encapsulation,cache 不會暴露在外被修改。

closure007

Conclusion


  • Closure 是 FP 的基礎,實務上最常使用的是 Function Factory
  • 透過 Closure + IIFE,也能以 function 實現 OOP 的 Encapsulation
2018-12-14