# JavaScript - Memoization 記憶函式
###### tags: `JavaScript` `Event Loop` `讀書會`
## 什麼是 Memoization ?
記憶函式 ( Memoization ) 是一種優化程式技巧,把需要大量計算 ( 長遞歸、長迭代操作 ) 的函式將其參數與結果緩存 ( Cache ),若調用記憶函式且給予相同參數時,不須經過計算程序,直接回傳相同結果。
<br>
## 沒有 Cache 機制的程式
假設有一個工人,這位工人需要去倉庫拿工具 ( getTool ) 來完成他的工作 ( task ) :
```javascript
function getTool (task) {
console.log('前往倉庫拿取工具')
return task + '工作完成'
}
```
<br>
但我們會發現,如果工人做同樣的工作 ( task ),他仍然會不斷跑去倉庫拿取工具 :
![](https://i.imgur.com/0Bmc6kt.png)
Day 1:工人 ( 小明 ) 第一天修理大水管,跑到倉庫拿工具。
Day 2:工人 ( 小明 ) 第二天繼續修理大水管,還是跑到倉庫拿工具。
Day 3:工人 ( 小明 ) 第三天繼續修理大水管,還是跑到倉庫拿工具
( 小明 OS : e04,給我工具箱 ! )。
<br>
### 身為哆啦工具夢的我們,來實現小明的願望吧 !
讓我們把拿取工具 ( getTool ) 過程優化 :
```javascript
function getTool (todoTask) {
const doneCache = {} // 已經做過同樣的工作 cache
return function () {
// 工作內容
const taskContent = JSON.stringify(arguments)
// 如果已經有做過同樣的工作
if (doneCache[taskContent]) {
return doneCache[taskContent] // 直接回傳工具對應的工作內容
}
// 如果工具箱沒有工作所需要的工具
else {
// 工作結果
const doTaskResult = todoTask.apply(this, arguments)
// cache 儲存工作結果,並回傳
doneCache[taskContent] = doTaskResult
return doTaskResult
}
}
}
```
然後訂定小明一個工作流程 :
```javascript
const work = getTool((task) => {
console.log('前往倉庫拿工具')
return task + ': 已完成工作'
})
```
<br>
如此一來,當小明做同樣的工作,他就知道已經有拿過工具,直接執行工作內容就可以了 !
![](https://i.imgur.com/bYzp4D9.png)
<br>
但是當小明每天做不同的工作,他還是得回去倉庫拿給西 ( 工具 ) :
![](https://i.imgur.com/zs4uanX.png)
<br>
## 設計一個 Momoized Function
每個功能函式都要寫成 Memoized Function 太麻煩,不妨設計一個 Memoize 工具函式吧 !
```javascript
const memoize = (func) => {
const cache = {}
return (...args) => {
const key = JSON.stringify(args)
if (cache[key] === undefined) {
cache[key] = func(...args)
}
return cache[key]
}
}
```
<br>
## Memoization 使用時機
- 當函式需要消耗大量效能來運算 ( Expensive function calls, heavy computations )。
- 經常會需要將前一次的值再次帶入的遞迴函式 ( Recursive function )。
<br>
## Memoization 需注意的問題
Cache 雖然可以優化執行效能,但 Cache 也會因處理大量資料 ( 有很多不同的資料 ) 時,占用大量記憶體且不會釋放,因此 Memoization 也是一把雙面刃,需要注意使用時機。
<br>
## Vue 也有相同的機制
Vue 也有提供給我們選擇使用 Memoization 或者不使用,也就是 Computed 及 Methods 方法。`computed` 帶有 Cache 機制,也就是在 Computed 它是屬於 Memoization,這也是為什麼 `computed` 都要求一定要 return 結果,否則會報錯,而 `methods` 就沒有 Cache 了,每次呼叫 Methods 的方法時都會重新執行。
此外,主流框架對於 Virtual DOM 渲染基本上都有 Cache 的機制,因此可以時常看見對於效能好的裝置來說,瀏覽有使用 Framework 的網頁時,整體網頁瀏覽較為順暢,但若是效能或記憶體容量較差的裝置來說,瀏覽使用 Framework 的網頁時常會有跑不動 ( Lag ) 的感覺。
<br>
---
## Memoization 運用在 Fibonacci ( 費波那契數列 )
費波納契數列 ( Fibonacci Sequence ) 指的是 :
```javascript
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
```
<br>
建立一個 Fibonacci 查詢函式
```javascript
// n 為查詢費波那契數列第幾筆的值
const fibonacci = n => {
return n <= 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2)
}
```
除了 `n = 1`、`n = 2` 的情況外,可以觀察每個 fibonacci 計算出來的值都是前兩個 fibonacci 相加的結果,也就是
```javascript
fibonacci(n) === fibonacci(n - 1) + fibonacci(n - 2)
```
<br>
### Time Complexity 時間複雜度
然而,利用這種初步的遞迴函式演算法是屬於 O(n^2),也就是當 n 的值愈大,運算所花費的時間會呈指數 ( 爆炸性 ) 成長 :
```javascript
fibonacci(3) // 執行 3 次函式
fibonacci(5) // 執行 9 次函式
fibonacci(7) // 執行 25 次函式
fibonacci(10) // 執行 109 次函式
fibonacci(20) // 執行 13259 次函式
fibonacci(30) // 執行 1664079 次函式
fibonacci(99) // 你不會想試的
```
![](https://i.imgur.com/y6xo28V.png =60%x)
圖片來源:[Learning Algorithms in JavaScript from Scratch](https://www.udemy.com/course/learning-algorithms-in-javascript-from-scratch/) by [Eric Traub](https://www.udemy.com/user/eric-traub/) @ Udemy
<br>
### Memoized Fibonacci
我們可以用上面設計的 Momoized Function 來封裝 `fibonacci` :
```javascript
const memoize = (func) => {
const cache = {}
return (...args) => {
const key = JSON.stringify(args)
if (cache[key] === undefined) {
cache[key] = func(...args)
}
return cache[key]
}
}
const fibonacci = memoize((n) => {
return n <= 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2)
})
```
<br>
## Reference
[JavaScript Memoization 故事介紹](https://medium.com/@vx3012564897/javascript-memoization-%E6%95%85%E4%BA%8B%E4%BB%8B%E7%B4%B9-d070b7e8699f)
[JavaScript記憶(Memoization)函數](https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/717300/)
[實現 JavaScript 的 Memoization](https://fred-zone.blogspot.com/2017/04/javascript-memorization.html)
[[演算法] Fibonacci:善用 cache 和 Memoization 提升程式效能](https://pjchender.blogspot.com/2017/09/fibonacci-cache-memoization.html)