iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 28
1

純函數

純函數是滿足以下條件才能稱為純函數

  • 當輸入參數相同時, 它是一個始終回傳相同的結果

例如呼叫以下 add 方法兩次, ans 結果始終一定是一樣

let ans = add(1, 2)
  • 不會產生副作用(Side Effect)

當在指定原本輸入的變數或物件時, 經過這個方法處理完畢之後, 如果會變動原本的變數或物件的值,就稱為是"具有副作用(Side Effect)".

例如下面 login 方法, 輸入參數 player 的內容被改變, 這叫做有 "副作用"

function login(player: PlayerInfo): boolean {
    player.status = "signed";
    return true;
}
  • 不依賴任何外部的變數

像下面 sayHello 方法需要外面的 count 變數當計數器, 這就是依賴外部的變數

let count: number = 1;
function sayHello(name: string): string {
    count++;
    return `${name}, login ${count} times.`;
}

一個典型的純函數的例子如下

function add(a: number, b: number): number {
    return a + b;
}

記憶化(memoization)

記憶化(memoization) 在程式開發技術中, 是一種提高程式碼執行速度的優化技術, 假設某個方法需要大量計算時間或大量資料才能得到結果並回傳回去, 當這個方法又再一次被呼叫, 而不用再一次計算來節省計算時間.

一個典型的大量計算例子就是遞迴計算階乘factorial 函數

function factorial(n: number): number {
   if( n <= 0 ) {
      return 1;
   }
   return n * factorial(n - 1);
}

如果階乘factorial 函數用了記憶化(memoization) 技術, 就可以提高階乘的性能, 也就是俗稱的快取(Cache) 技術.

閉包(Closure)

閉包(Closure) 就是帶有狀態的函數, 以下示範了甚麼是閉包函數

function addCounter() {
    let counter = 0;
    return function() {
        counter = counter + 1
        return counter;
    }
}

當你呼叫上面的 addCounter 函數放到 obj 變數, 然後你呼叫 obj() 函數它會輸出 1, 再次呼叫 obj() 函數, 它會輸出 2, 以此類推...

let obj = addCounter();
obj();
obj();

之所以提到這個閉包函數, 我們可以用來製作具有 "快取" 的函數.

下面的 memoize 函數, 提供一個輸入參數, 參數類型是 "方法"

function memoize(fn: Function) {
    const cache: any = {};
    return function(...args: any[]) {
        const argsKey = JSON.stringify(args);
        if( !cache.hasOwnProperty(argsKey) ) {
            let result = fn(...args);
            cache[argsKey] = result;
            return result;
        }
        return cache[argsKey];
    }
}

上面程式碼表明 memoize 函數帶有"狀態", 就是 cache 變數.

然後 memoize 函數回傳一個新的函數. 在新的函數中, 會把輸入參數的內容先轉換為 cache key 值, 如下

const argsKey = JSON.stringify(args);

然後新函數檢查快取內容是否有這個 key 存在? 如果 cache 不存在, 就呼叫 fn 函數取得結果, 並將結果存入快取, 再回傳結果.

if( !cache.hasOwnProperty(argsKey) ) {
    let result = fn(...args);
    cache[argsKey] = result;
    return result;
}

如果快取內容已經存在, 就回傳快取結果.

return cache[argsKey];

要使用這個 memoize 函數, 可以這樣使用, 例如快取 add 方法

let cachedAdd = memoize((a: number, b: number) => {
    return a + b;
});

然後在程式中使用 cachedAdd 函數, 就有具有 add 的快取功能了

let c = cachedAdd(1, 2);
console.log(c);

為了證明這個快取機制是有效的, 你可以修改程式碼, 增加計數器, 計算 a + b 被呼叫幾次

let counter = 0;
let cachedAdd = memoize((a: number, b: number) => {
    counter++;
    return a + b;
});

呼叫看看以下程式碼 cachedAdd(1,2) 兩次, 看看計數器的值是多少?

let c = 0;
c = cachedAdd(1, 2);
c = cachedAdd(1, 2);
console.log(c);
console.log(counter);

具有快取功能的 cachedAdd 函數, 相同的輸入參數呼叫 cachedAdd 函數 N 次, 預期計數器的值應該是 1.


上一篇
泛型柯里化 - 27
下一篇
快取裝飾器 - 29
系列文
為什麼世界需要Typescript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言