使用JavaScrip實(shí)現(xiàn)一個(gè)記憶函數(shù)
說在前面
在編程的世界里,性能優(yōu)化始終是一個(gè)重要的話題。今天,我們將一起來實(shí)現(xiàn)一個(gè)實(shí)用的記憶函數(shù)(簡(jiǎn)單來說,就是同樣的入?yún)?,只?huì)在第一次調(diào)用指定函數(shù)獲取結(jié)果,后續(xù)則可以直接獲取到第一次計(jì)算的結(jié)果返回),它能夠顯著提升函數(shù)調(diào)用的效率,特別是在處理重復(fù)計(jì)算的場(chǎng)景中。
需求
現(xiàn)給定一個(gè)函數(shù) fn
,返回該函數(shù)的一個(gè) 記憶化
版本。
一個(gè) 記憶化
的函數(shù)是一個(gè)函數(shù),它不會(huì)被相同的輸入調(diào)用兩次。而是會(huì)返回一個(gè)緩存的值。
函數(shù) fn
可以是任何函數(shù),對(duì)它所接受的值類型沒有任何限制。如果兩個(gè)輸入值在 JavaScript
中使用 ===
運(yùn)算符比較時(shí)相等,則它們被視為相同。
示例1
輸入:
getInputs = () => [[2,2],[2,2],[1,2]] fn = function (a, b) { return a + b; }
輸出:
[{"val":4,"calls":1},{"val":4,"calls":1},{"val":3,"calls":2}]
解釋:
const inputs = getInputs(); const memoized = memoize(fn); for (const arr of inputs) { memoized(...arr); }
對(duì)于參數(shù)為 (2, 2) 的輸入: 2 + 2 = 4,需要調(diào)用 fn() 。
對(duì)于參數(shù)為 (2, 2) 的輸入: 2 + 2 = 4,這些輸入之前已經(jīng)出現(xiàn)過,因此不需要再次調(diào)用 fn()。
對(duì)于參數(shù)為 (1, 2) 的輸入: 1 + 2 = 3,需要再次調(diào)用 fn(),總共調(diào)用了 2 次。
示例2
輸入:
getInputs = () => [[{},{}],[{},{}],[{},{}]] fn = function (a, b) { return a + b; }
輸出:
[{"val":{},"calls":1...
解釋:
將兩個(gè)空對(duì)象合并總是會(huì)得到一個(gè)空對(duì)象。盡管看起來應(yīng)該緩存命中并只調(diào)用一次 fn(),但是這些空對(duì)象彼此之間都不是 === 相等的。
示例3
輸入:
getInputs = () => { const o = {}; return [[o,o],[o,o],[o,o]]; } fn = function (a, b) { return ({...a, ...b}); }
輸出:
[{"val":{},"calls":1},{"val":{},"calls":1},{"val":{},"calls":1}]
解釋:
將兩個(gè)空對(duì)象合并總是會(huì)得到一個(gè)空對(duì)象。因?yàn)閭魅氲拿總€(gè)對(duì)象都是相同的,所以第二個(gè)和第三個(gè)函數(shù)調(diào)用都會(huì)命中緩存。
代碼實(shí)現(xiàn)
1、入?yún)⑻幚?/h3>
要避免重復(fù)計(jì)算相同入?yún)ⅲ俏覀兙托枰獙⒚看斡?jì)算的入?yún)⒂涗浧饋?,但是入?yún)?huì)有多個(gè),我們還需要將所有入?yún)⒁灿涗浧饋?,那么怎么將多個(gè)入?yún)⑥D(zhuǎn)成一個(gè)key
呢?
首先我們先簡(jiǎn)化一下,如果入?yún)⒍际亲址脑捨覀儠?huì)怎么進(jìn)行處理?
沒錯(cuò),都是字符串的話我們可以直接使用連接符號(hào)將所有入?yún)⑦B接起來作為一個(gè)key
。那么現(xiàn)在入?yún)?shù)據(jù)格式多種的情況我們應(yīng)該要怎么處理呢?
既然入?yún)⒍际亲址臅r(shí)候我們可以處理,那我們就給每一個(gè)入?yún)①x予一個(gè)專屬的id,如下圖:
const idMap = new Map(); const getId = (k) => { if (idMap.has(k)) { return idMap.get(k); } const size = idMap.size; idMap.set(k, size); return size; };
這個(gè)函數(shù)用于為傳入的參數(shù)生成一個(gè)唯一的標(biāo)識(shí)符。它首先檢查 idMap
中是否已經(jīng)存在該參數(shù)對(duì)應(yīng)的標(biāo)識(shí)符,如果存在,則直接返回;如果不存在,就將當(dāng)前 idMap
的大小作為新的標(biāo)識(shí)符,并將參數(shù)與標(biāo)識(shí)符的映射關(guān)系存儲(chǔ)到 idMap
中。
因?yàn)檫@里的入?yún)⒖梢允侨我忸愋偷臄?shù)據(jù),所以我們這里不能直接用Object對(duì)象
來記錄,可以使用Map
來記錄。
const a = {aa:1}; const b = {}; const c = new Map(); b[a] = 1; c.set(a,1); console.log(b); console.log(c);
這里我們得到的b為:
{ "[object Object]": 1 }
c為:
new Map([ [ { "aa": 1 }, 1 ] ])
2、記錄入?yún)?/h3>
遍歷arguments
,獲取到每一個(gè)入?yún)⒌膶賗d,最后使用-
將所有id
連接起來作為一組入?yún)⒌?code>key,將計(jì)算得到的值作為value
保存。
const arr = []; for (const element of arguments) { arr.push(getId(element)); } const key = arr.join("-"); if (!valMap.has(key)) { valMap.set(key, fn(...arguments)); } return valMap.get(key);
3、完整代碼
/** * @param {Function} fn * @return {Function} */ function memoize(fn) { const idMap = new Map(); const valMap = new Map(); const getId = (k) => { if (idMap.has(k)) { return idMap.get(k); } const size = idMap.size; idMap.set(k, size); return size; }; return function () { const arr = []; for (const element of arguments) { arr.push(getId(element)); } const key = arr.join("-"); if (!valMap.has(key)) { valMap.set(key, fn(...arguments)); } return valMap.get(key); }; }
應(yīng)用場(chǎng)景
假設(shè)我們有一個(gè)復(fù)雜的對(duì)象,其中某個(gè)屬性的計(jì)算需要消耗大量資源,并且該屬性可能會(huì)被多次訪問。
const complexObject = { data: { // 大量復(fù)雜數(shù)據(jù) }, getComputedProperty: memoize(function () { // 復(fù)雜的計(jì)算邏輯,例如遍歷 data 中的數(shù)據(jù)進(jìn)行統(tǒng)計(jì)分析 return result; }) }; console.log(complexObject.getComputedProperty()); console.log(complexObject.getComputedProperty());
在第一次訪問 complexObject.getComputedProperty
時(shí),會(huì)執(zhí)行復(fù)雜的計(jì)算邏輯并緩存結(jié)果。后續(xù)再次訪問時(shí),直接返回緩存的結(jié)果,避免了重復(fù)計(jì)算,提高了整體性能。
到此這篇關(guān)于使用JavaScrip實(shí)現(xiàn)一個(gè)記憶函數(shù)的文章就介紹到這了,更多相關(guān)JavaScrip函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解javascript腳本何時(shí)會(huì)被執(zhí)行
這篇文章主要介紹了詳解javascript腳本何時(shí)會(huì)被執(zhí)行,幫助大家更好的理解和使用JavaScript,感興趣的朋友可以了解下2021-02-02概述如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的瀏覽器端js模塊加載器
本文主要對(duì)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的js加載器的步驟進(jìn)行介紹--主要可以分為解析路徑、下載模塊、解析模塊依賴、解析模塊四個(gè)步驟。需要的朋友來看下吧2016-12-12JavaScript錯(cuò)誤處理操作實(shí)例詳解
這篇文章主要介紹了JavaScript錯(cuò)誤處理操作,結(jié)合實(shí)例形式分析了javascript常見的錯(cuò)誤類型、錯(cuò)誤處理語句以及相關(guān)使用技巧,需要的朋友可以參考下2019-01-01微信小程序 this.triggerEvent()的具體使用
這篇文章主要介紹了微信小程序 this.triggerEvent()的具體使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12JavaScript 申明函數(shù)的三種方法 每個(gè)函數(shù)就是一個(gè)對(duì)象(一)
JavaScript 申明函數(shù)的三種方法 每個(gè)函數(shù)就是一個(gè)對(duì)象(一)2009-12-12微信小程序?qū)崿F(xiàn)多行文字超出部分省略號(hào)顯示功能
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)多行文字 超出部分省略號(hào)顯示功能,比如設(shè)置只顯示2行,超出部分省略號(hào)顯示,本文通過實(shí)例代碼給大家介紹,需要的朋友可以參考下2019-10-10JavaScript中的for...of和for...in循環(huán)容易遇到的問題及解決方法總結(jié)
在 JavaScript 編程中,for...of 和 for...in 是常用的循環(huán)語法,但它們?cè)谑褂脮r(shí)可能會(huì)引發(fā)一些意想不到的問題,本文將分享我在使用這兩種循環(huán)時(shí)所遇到的坑和經(jīng)驗(yàn),需要的朋友可以參考下2023-08-08理解JAVASCRIPT中hasOwnProperty()的作用
JavaScript中hasOwnProperty函數(shù)方法是返回一個(gè)布爾值,指出一個(gè)對(duì)象是否具有指定名稱的屬性2013-06-06