JavaScript 高級技巧 Memoization

memoization 來源於拉丁語 memorandum ("to be remembered"),不要與 memorization 混淆了。javascript

首先來看一下維基百科的描述:java

In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

簡單來講,memoization 是一種優化技術,主要用於經過存儲昂貴的函數調用的結果來加速計算機程序,並在再次發生相同的輸入時返回緩存的結果。git

本文首先介紹一個簡單的使用 memoization 優化技術的例子,而後解讀 underscore 和 reselect 庫中使用 memoization 的源碼,加深理解。github

階乘

不使用 memoization

不假思索,咱們會當即寫下以下的代碼:redux

const factorial = n => {
    if (n === 1) {
        return 1
    } else {
        return factorial(n - 1) * n
    }
};

使用 memoization

const cache = []
const factorial = n => {
    if (n === 1) {
        return 1
    } else if (cache[n - 1]) {
        return cache[n - 1]
    } else {
        let result = factorial(n - 1) * n
        cache[n - 1] = result
        return result
    }
};

使用 閉包 和 memoization

常見的方式是 閉包 和 memoization 一塊兒搭配使用:緩存

const factorialMemo = () => {
    const cache = []
    const factorial = n => {
        if (n === 1) {
            return 1
        } else if (cache[n - 1]) {
            console.log(`get factorial(${n}) from cache...`)
            return cache[n - 1]
        } else {
            let result = factorial(n - 1) * n
            cache[n - 1] = result
            return result
        }
    }
    return factorial
};
const factorial = factorialMemo();

繼續變形,下面這種編寫方式是最多見的形式。閉包

const factorialMemo = func => {
    const cache = []
    return function(n) {
        if (cache[n - 1]) {
            console.log(`get factorial(${n}) from cache...`)
            return cache[n - 1]
        } else {
            const result = func.apply(null, arguments)
            cache[n - 1] = result
            return result
        }
    }
}

const factorial = factorialMemo(function(n) {
    return n === 1 ? 1 : factorial(n - 1) * n
});

從階乘的這個例子能夠知道 memoization 是一個空間換時間的方式,存儲執行結果,下次再次發生相同的輸入會直接輸出結果,提升了執行的速度。app

underscore 源碼中的 memoization

// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
    var memoize = function(key) {
        var cache = memoize.cache;
        var address = '' + (hasher ? hasher.apply(this, arguments) : key);
        if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
        return cache[address];
    };
    memoize.cache = {};
    return memoize;
};

代碼一目瞭然,使用 _.memoize 來實現階乘以下:函數

const factorial = _.memoize(function(n) {
    return n === 1 ? 1 : factorial(n - 1) * n
});

參照這個源碼,上面的階乘繼續能夠變形以下:優化

const factorialMemo = func => {
    const memoize = function(n) {
        const cache = memoize.cache
        if (cache[n - 1]) {
            console.log(`get factorial(${n}) from cache...`)
            return cache[n - 1]
        } else {
            const result = func.apply(null, arguments)
            cache[n - 1] = result
            return result
        }
    }
    memoize.cache = []
    return memoize
}

const factorial = factorialMemo(function(n) {
    return n === 1 ? 1 : factorial(n - 1) * n
});

reselect 源碼中的 memoization

export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
    let lastArgs = null
    let lastResult = null
    // we reference arguments instead of spreading them for performance reasons
    return function () {
        if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
            // apply arguments instead of spreading for performance.
            lastResult = func.apply(null, arguments)
        }

        lastArgs = arguments
        return lastResult
    }
};

從源碼能夠知道當 lastArgs 與 arguments 相同的時候,就不會再執行 func。

總結

memoization 是一種優化技術,避免一些沒必要要的重複計算,能夠提升計算速度。

參考

  1. Memoization wiki
  2. Understanding JavaScript Memoization In 3 Minutes
  3. Underscore
  4. reselect
  5. Implementing Memoization in JavaScript
相關文章
相關標籤/搜索