使用記憶器進行繁重的計算功能
如果要在處理器(客戶端或伺服器端)上構建可能很重的函式,則可能需要考慮一個 memoizer ,它是先前函式執行的快取及其返回值。這允許你檢查之前是否傳遞了函式的引數。請記住,純函式是那些給出輸入,返回相應的唯一輸出並且不會在其範圍之外引起副作用的函式,所以,不應該將 memoizers 新增到不可預測的函式或依賴於外部資源的函式(如 AJAX 呼叫或隨機返回值)。
假設我有一個遞迴因子函式:
function fact(num) {
return (num === 0)? 1 : num * fact(num - 1);
}
例如,如果我將小值從 1 傳遞到 100,那麼就沒有問題,但是一旦我們開始深入,我們可能會炸燬呼叫堆疊或者讓我們正在執行此操作的 Javascript 引擎有點痛苦,特別是如果引擎不計算尾部呼叫優化(儘管 Douglas Crockford 說本機 ES6 包含尾部呼叫優化)。
我們可以硬編碼我們自己的字典從 1 到上帝知道什麼數字與他們相應的階乘,但是,我不確定我是否建議! 讓我們建立一個 memoizer,好嗎?
var fact = (function() {
var cache = {}; // Initialise a memory cache object
// Use and return this function to check if val is cached
function checkCache(val) {
if (val in cache) {
console.log('It was in the cache :D');
return cache[val]; // return cached
} else {
cache[val] = factorial(val); // we cache it
return cache[val]; // and then return it
}
/* Other alternatives for checking are:
|| cache.hasOwnProperty(val) or !!cache[val]
|| but wouldn't work if the results of those
|| executions were falsy values.
*/
}
// We create and name the actual function to be used
function factorial(num) {
return (num === 0)? 1 : num * factorial(num - 1);
} // End of factorial function
/* We return the function that checks, not the one
|| that computes because it happens to be recursive,
|| if it weren't you could avoid creating an extra
|| function in this self-invoking closure function.
*/
return checkCache;
}());
現在我們可以開始使用它了:
現在我開始思考我做了什麼,如果我從 1 遞增而不是從 num 遞減,我可以遞迴地快取快取中從 1 到 num 的所有因子,但我會留給你。
這很好但是如果我們有多個引數怎麼辦?這是個問題?不完全是,我們可以做一些很好的技巧,比如在 arguments 陣列上使用 JSON.stringify()
,或者甚至是函式依賴的值列表(對於物件導向的方法)。這樣做是為了生成一個包含所有引數和依賴項的唯一鍵。
我們還可以建立一個 memoizes
其他函式的函式,使用與之前相同的作用域概念(返回使用原始函式且可以訪問快取物件的新函式):
警告:ES6 語法,如果你不喜歡它,替換…什麼都沒有,並使用 var args = Array.prototype.slice.call(null, arguments);
技巧; 用 var 替換 const 和 let,以及你已經知道的其他東西。
function memoize(func) {
let cache = {};
// You can opt for not naming the function
function memoized(...args) {
const argsKey = JSON.stringify(args);
// The same alternatives apply for this example
if (argsKey in cache) {
console.log(argsKey + ' was/were in cache :D');
return cache[argsKey];
} else {
cache[argsKey] = func.apply(null, args); // Cache it
return cache[argsKey]; // And then return it
}
}
return memoized; // Return the memoized function
}
現在請注意,這將適用於多個引數但在物件導向的方法中沒有多大用處,我認為,你可能需要一個額外的依賴物件。此外,func.apply(null, args)
可以替換為 func(...args)
,因為陣列解構將分別傳送它們而不是作為陣列形式。另外,僅供參考,將陣列作為引數傳遞給 func 將不起作用,除非你像我一樣使用 Function.prototype.apply
。
要使用上述方法,你只需:
const newFunction = memoize(oldFunction);
// Assuming new oldFunction just sums/concatenates:
newFunction('meaning of life', 42);
// -> "meaning of life42"
newFunction('meaning of life', 42); // again
// => ["meaning of life",42] was/were in cache :D
// -> "meaning of life42"