本文已整理到 Github,地址 ?? blog。
如果我的內(nèi)容幫助到了您,歡迎點(diǎn)個(gè) Star ?????? 鼓勵(lì)鼓勵(lì) :) ~~
我希望我的內(nèi)容可以幫助你?,F(xiàn)在我專注于前端領(lǐng)域,但我也將分享我在有限的時(shí)間內(nèi)看到和感受到的東西。
Memoization — 函數(shù)記憶或函數(shù)緩存,是一種優(yōu)化技術(shù),用于許多編程語言中,以減少冗余、昂貴的函數(shù)調(diào)用。這是通過緩存函數(shù)的返回值來實(shí)現(xiàn)的。
以下是一個(gè)昂貴的函數(shù)調(diào)用,它以一種非常低效的方式找到數(shù)字的平方:
const inefficientSquare = (num) => {
let total = 0
for (let i = 0; i < num; i++) {
for (let j = 0; j < num; j++) {
total++
}
}
return total
}
我們可以使用相同的值運(yùn)行此函數(shù),每次都需要一段時(shí)間才能執(zhí)行。
const start = new Date()
inefficientSquare(40000)
console.log(new Date() - start) // 1278
const start2 = new Date()
inefficientSquare(40000)
console.log(new Date() - start2) // 1245
每一次執(zhí)行,它都會(huì)重新計(jì)算!
為 memoize 編寫偽代碼
在編寫任何代碼之前,讓我們先通過 Memoizer 進(jìn)行推理。
- 引用函數(shù)作為輸入
- 返回一個(gè)函數(shù)
- 創(chuàng)建某種形式的緩存以保存先前函數(shù)調(diào)用的結(jié)果
- 以后調(diào)用函數(shù)時(shí),如果存在緩存結(jié)果,則返回緩存結(jié)果
- 如果緩存的值不存在,則調(diào)用函數(shù)并將結(jié)果存儲(chǔ)在緩存中
編碼時(shí)間
這是上述偽代碼概述的實(shí)現(xiàn)。如引言中所述,這是次優(yōu)的,您不應(yīng)該在生產(chǎn)中使用它。我會(huì)解釋為什么!
// 引用函數(shù)
const memoize = (func) => {
// 創(chuàng)建結(jié)果緩存
const results = {}
// 返回函數(shù)
return (...args) => {
// 為結(jié)果緩存創(chuàng)建鍵
const argsKey = JSON.stringify(args)
// 僅在沒有緩存值時(shí)執(zhí)行 func
if (!results[argsKey]) {
// 將函數(shù)調(diào)用結(jié)果存儲(chǔ)在緩存中
results[argsKey] = func(...args)
}
// 返回緩存值
return results[argsKey]
}
}
這個(gè)實(shí)現(xiàn)會(huì)有一些缺點(diǎn),它使用 JSON.stringify 在結(jié)果緩存中創(chuàng)建鍵。而 JSON.stringify 最大問題是它不能序列化某些輸入,如函數(shù)和 Symbol(以及 JSON 中找不到的任何內(nèi)容)。
測試效果
const inefficientSquare = memoize((num) => {
let total = 0
for (let i = 0; i < num; i++) {
for (let j = 0; j < num; j++) {
total++
}
}
return total
})
const start = new Date()
inefficientSquare(40000)
console.log(new Date() - start) // 1251
const start2 = new Date()
inefficientSquare(40000)
console.log(new Date() - start2) // 0
我們可以看到,在執(zhí)行第二次時(shí),輸入相同的內(nèi)容,它不需要時(shí)間重新計(jì)算;我們只是從一個(gè)對(duì)象中提取緩存的值。
注意:它可以節(jié)省計(jì)算時(shí)間,提高讀取性能,讀取速度更快。但它同時(shí)也會(huì)消耗更多的內(nèi)存來保存以前的結(jié)果(用內(nèi)存換區(qū)性能)。
應(yīng)用場景
它常用于解決同參數(shù)的多次執(zhí)行問題,大量的循環(huán)或遞歸調(diào)用的數(shù)學(xué)運(yùn)算或判斷。
以下是一個(gè)斐波那契數(shù)列示例:
const factorial = memoize((n) => (n <= 1 ? 1 : n * factorial(n - 1)))
factorial(5)
函數(shù)記憶僅在純函數(shù)中有效
函數(shù)記憶很棒,但僅在您的純函數(shù)中有效。換句話說,如果函數(shù)的返回值依賴于多個(gè)輸入,那么這些輸入的緩存值并不總是正確的。此外,如果您的函數(shù)有副作用,那么 memoizer 不會(huì)復(fù)制這些副作用,它只會(huì)返回最終返回的函數(shù)值。