閱讀前,請先封印以下能力:類、閉包、繼承&多型、高階函式……
現在,你只會全域變數和函式,開始寫一個帶 cache 的 Fibonacci,
const cache = new Map();
const fib = n => {
if (cache.has(n)) {
console.log("use cache", n);
return cache.get(n);
} else {
let result;
if (n === 1 || n === 2) result = 1;
else result = fib(n - 1) + fib(n - 2);
cache.set(n, result);
return result;
}
};
fib(10);
再要求你寫幾十個類似的函式,你會陷入兩難的境地:是把全域變數定義在操作它的函式附近,還是把全體全域變數定義在一處好?
- 把全域變數定義在操作它的函式附近,容易因為變數名沖突造成程式錯誤,
- 把全域變數定義在一處,代碼不好拆分成獨立檔案,導致不好復用,

引入命名空間是緩解全域變數污染的解法,使用面向物件的類是消除全域變數的解法,
類把變數和操作變數的函式聚在一起,變數不再是全域的,從而減少了全域變數,

class FibCalculator {
#cache = new Map();
calc(n) {
if (this.#cache.has(n)) {
console.log("use cache", n);
return this.#cache.get(n);
} else {
let result;
if (n === 1 || n === 2) result = 1;
else
result = this.calc(n - 1) + this.calc(n - 2);
this.#cache.set(n, result);
return result;
}
}
}
const fib = new FibCalculator();
fib.calc(10);
函式的閉包也一樣,把變數和操作變數的函式聚在一起,變數不再是全域的,
const fib = (function () {
const cache = new Map();
const fib = n => {
if (cache.has(n)) {
console.log("use cache", n);
return cache.get(n);
} else {
let result;
if (n === 1 || n === 2) result = 1;
else result = fib(n - 1) + fib(n - 2);
cache.set(n, result);
return result;
}
};
return fib;
})();
閉包等價于「只有一個函式的物件」,可以用閉包替代下圖中的 class A 和 class B,

類、閉包解決了全域變數的問題,我們再來談代碼復用的問題,有兩種復用:
- 復用整個代碼塊
- 復用代碼塊的流程
還以這段 Fibonacci 為例:
class FibCalculator {
#cache = new Map();
calc(n) {
if (this.#cache.has(n)) {
console.log("use cache", n);
return this.#cache.get(n);
} else {
let result;
if (n === 1 || n === 2) result = 1;
else
result = this.calc(n - 1) + this.calc(n - 2);
this.#cache.set(n, result);
return result;
}
}
}
const fib = new FibCalculator();
fib.calc(10);
程式需要計算 Fibonacci 時,可以匯入 class, new 出實體,實作復用,這個復用就是「復用整個代碼塊」,
另外,不管是計算 Fibonacci 還是計算 Factorial, cache 的邏輯都是一樣的:
- 添加一個 cache 私有變數
- 計算前先看 cache 中有沒有
- 有就直接回傳
- 沒有則計算,計算完了存入 cache,再回傳
復用 cache 的邏輯就是我說的「復用代碼塊的流程」,
面向物件是靠繼承&多型實作「復用代碼塊的流程」的,
class Calculator {
calc(n) {}
}
class CachedCalculator extends Calculator {
#cache = new Map();
#calculator;
constructor(calculator) {
super();
this.#calculator = calculator;
}
calc(n) {
if (this.#cache.has(n)) {
console.log("use cache", n);
return this.#cache.get(n);
} else {
const result = this.#calculator.calc(n);
this.#cache.set(n, result);
return result;
}
}
}
class FibCalculator extends Calculator {
calc(n) {
if (n === 1 || n === 2) return 1;
else return this.calc(n - 1) + this.calc(n - 2);
}
}
class FactorialCaculator extends Calculator {
calc(n) {
if (n === 1) return 1;
else return n * this.calc(n - 1);
}
}
const fib = new CachedCalculator(
new FibCalculator()
);
fib.calc(10);
const factorial = new CachedCalculator(
new FactorialCaculator()
);
factorial.calc(10);
有些看官也許看出這版 cache 有問題,遞回的部分并沒有存入 cache,計算 fib.calc(10),按理說,1-9 都計算了一遍,但 cache 中只存了 10 的結果,代碼改進一下,讓遞回的部分也存入 cache,
class Calculator {
calc(n, self) {}
}
class CachedCalculator extends Calculator {
#cache = new Map();
#calculator;
constructor(calculator) {
super();
this.#calculator = calculator;
}
calc(n, self = null) {
if (this.#cache.has(n)) {
console.log("use cache", n);
return this.#cache.get(n);
} else {
const result = this.#calculator.calc(n, this);
this.#cache.set(n, result);
return result;
}
}
}
class FibCalculator extends Calculator {
calc(n, self) {
if (n === 1 || n === 2) return 1;
else return self.calc(n - 1) + self.calc(n - 2);
}
}
class FactorialCaculator extends Calculator {
calc(n, self) {
if (n === 1) return 1;
else return n * self.calc(n - 1);
}
}
const fib = new CachedCalculator(
new FibCalculator()
);
fib.calc(10);
const factorial = new CachedCalculator(
new FactorialCaculator()
);
factorial.calc(10);
函式式是靠高階函式「復用代碼塊的流程」的,之前寫過一篇高階函式的博客,這里就不贅述了,感興趣的同學可以點這里,
最后,把面向物件和函式式放到表格里對比一下:
| 問題 | 面向物件 | 函式式 |
|---|---|---|
| 消除全域變數 | 類&物件 | 閉包 |
| 復用代碼 | 繼承&多型 | 高階函式 |
盡管面向物件和函式式代碼表現形式不一樣,但解決的問題卻是同樣的,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/270873.html
標籤:JavaScript
