您好,登錄后才能下訂單哦!
這篇文章主要介紹JavaScript緩存的案例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
隨著我們的應(yīng)用程序的不斷增長(zhǎng)并開始進(jìn)行復(fù)雜的計(jì)算時(shí),對(duì)速度的需求越來越高,所以流程的優(yōu)化變得必不可少。 當(dāng)我們忽略這個(gè)問題時(shí),我們最終的程序需要花費(fèi)大量時(shí)間并在執(zhí)行期間消耗大量的系統(tǒng)資源。
緩存是一種優(yōu)化技術(shù),通過存儲(chǔ)開銷大的函數(shù)執(zhí)行的結(jié)果,并在相同的輸入再次出現(xiàn)時(shí)返回已緩存的結(jié)果,從而加快應(yīng)用程序的速度。
如果這對(duì)你沒有多大意義,那沒關(guān)系。 本文深入解釋了為什么需要進(jìn)行緩存,緩存是什么,如何實(shí)現(xiàn)以及何時(shí)應(yīng)該使用緩存。
什么是緩存
緩存是一種優(yōu)化技術(shù),通過存儲(chǔ)開銷大的函數(shù)執(zhí)行的結(jié)果,并在相同的輸入再次出現(xiàn)時(shí)返回已緩存的結(jié)果,從而加快應(yīng)用程序的速度。
在這一點(diǎn)上,我們很清楚,緩存的目的是減少執(zhí)行“昂貴的函數(shù)調(diào)用”所花費(fèi)的時(shí)間和資源。
什么是昂貴的函數(shù)調(diào)用?別搞混了,我們不是在這里花錢。在計(jì)算機(jī)程序的上下文中,我們擁有的兩種主要資源是時(shí)間和內(nèi)存。因此,一個(gè)昂貴的函數(shù)調(diào)用是指一個(gè)函數(shù)調(diào)用中,由于計(jì)算量大,在執(zhí)行過程中大量占用了計(jì)算機(jī)的資源和時(shí)間。
然而,就像對(duì)待金錢一樣,我們需要節(jié)約。為此,使用緩存來存儲(chǔ)函數(shù)調(diào)用的結(jié)果,以便在將來的時(shí)間內(nèi)快速方便地訪問。
緩存只是一個(gè)臨時(shí)的數(shù)據(jù)存儲(chǔ),它保存數(shù)據(jù),以便將來對(duì)該數(shù)據(jù)的請(qǐng)求能夠更快地得到處理。
因此,當(dāng)一個(gè)昂貴的函數(shù)被調(diào)用一次時(shí),結(jié)果被存儲(chǔ)在緩存中,這樣,每當(dāng)在應(yīng)用程序中再次調(diào)用該函數(shù)時(shí),結(jié)果就會(huì)從緩存中非??焖俚厝〕?,而不需要重新進(jìn)行任何計(jì)算。
為什么緩存很重要?
下面是一個(gè)實(shí)例,說明了緩存的重要性:
想象一下,你正在公園里讀一本封面很吸引人的新小說。每次一個(gè)人經(jīng)過,他們都會(huì)被封面吸引,所以他們會(huì)問書名和作者。第一次被問到這個(gè)問題的時(shí)候,你翻開書,讀出書名和作者的名字?,F(xiàn)在越來越多的人來這里問同樣的問題。你是一個(gè)很好的人,所以你回答所有問題。
你會(huì)翻開封面,把書名和作者的名字一一告訴他,還是開始憑記憶回答?哪個(gè)能節(jié)省你更多的時(shí)間?
發(fā)現(xiàn)其中的相似之處了嗎?使用記憶法,當(dāng)函數(shù)提供輸入時(shí),它執(zhí)行所需的計(jì)算并在返回值之前將結(jié)果存儲(chǔ)到緩存中。如果將來接收到相同的輸入,它就不必一遍又一遍地重復(fù),它只需要從緩存(內(nèi)存)中提供答案。
緩存是怎么工作的
JavaScript 中的緩存的概念主要建立在兩個(gè)概念之上,它們分別是:
閉包是函數(shù)和聲明該函數(shù)的詞法環(huán)境的組合。
不是很清楚? 我也這么認(rèn)為。
為了更好的理解,讓我們快速研究一下 JavaScript 中詞法作用域的概念,詞法作用域只是指程序員在編寫代碼時(shí)指定的變量和塊的物理位置。如下代碼:
function foo(a) { var b = a + 2; function bar(c) { console.log(a, b, c); } bar(b * 2); } foo(3); // 3, 5, 10
從這段代碼中,我們可以確定三個(gè)作用域:
foo
作為唯一標(biāo)識(shí)符)foo
作用域,它有標(biāo)識(shí)符 a
、b
和 bar
bar
作用域,包含 c
標(biāo)識(shí)符仔細(xì)查看上面的代碼,我們注意到函數(shù) foo
可以訪問變量 a 和 b,因?yàn)樗短自?foo
中。注意,我們成功地存儲(chǔ)了函數(shù) bar
及其運(yùn)行環(huán)境。因此,我們說 bar
在 foo
的作用域上有一個(gè)閉包。
你可以在遺傳的背景下理解這一點(diǎn),即個(gè)體有機(jī)會(huì)獲得并表現(xiàn)出遺傳特征,即使是在他們當(dāng)前的環(huán)境之外,這個(gè)邏輯突出了閉包的另一個(gè)因素,引出了我們的第二個(gè)主要概念。
通過接受其他函數(shù)作為參數(shù)或返回其他函數(shù)的函數(shù)稱為高階函數(shù)。
閉包允許我們?cè)诜忾]函數(shù)的外部調(diào)用內(nèi)部函數(shù),同時(shí)保持對(duì)封閉函數(shù)的詞法作用域的訪問
讓我們對(duì)前面的示例中的代碼進(jìn)行一些調(diào)整,以解釋這一點(diǎn)。
function foo(){ var a = 2; function bar() { console.log(a); } return bar; } var baz = foo(); baz();//2
注意函數(shù) foo
如何返回另一個(gè)函數(shù) bar
。這里我們執(zhí)行函數(shù) foo
并將返回值賦給baz
。但是在本例中,我們有一個(gè)返回函數(shù),因此,baz
現(xiàn)在持有對(duì) foo
中定義的bar
函數(shù)的引用。
最有趣的是,當(dāng)我們?cè)?foo
的詞法作用域之外執(zhí)行函數(shù) baz
時(shí),仍然會(huì)得到 a
的值,這怎么可能呢?
請(qǐng)記住,由于閉包的存在,bar
總是可以訪問 foo
中的變量(繼承的特性),即使它是在 foo
的作用域之外執(zhí)行的。
案例研究:斐波那契數(shù)列
斐波那契數(shù)列是什么?
斐波那契數(shù)列是一組數(shù)字,以1 或 0 開頭,后面跟著1,然后根據(jù)每個(gè)數(shù)字等于前兩個(gè)數(shù)字之和規(guī)則進(jìn)行。如
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …
或者
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …
挑戰(zhàn):編寫一個(gè)函數(shù)返回斐波那契數(shù)列中的 n 元素,其中的序列是:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …]
知道每個(gè)值都是前兩個(gè)值的和,這個(gè)問題的遞歸解是:
function fibonacci(n) { if (n <= 1) { return 1 } return fibonacci(n - 1) + fibonacci(n - 2) }
確實(shí)簡(jiǎn)潔準(zhǔn)確!但是,有一個(gè)問題。請(qǐng)注意,當(dāng) n
的值到終止遞歸之前,需要做大量的工作和時(shí)間,因?yàn)樾蛄兄写嬖趯?duì)某些值的重復(fù)求值。
看看下面的圖表,當(dāng)我們?cè)噲D計(jì)算 fib(5)
時(shí),我們注意到我們反復(fù)地嘗試在不同分支的下標(biāo) 0,1,2,3
處找到 Fibonacci 數(shù),這就是所謂的冗余計(jì)算,而這正是緩存所要消除的。
function fibonacci(n, memo) { memo = memo || {} if (memo[n]) { return memo[n] } if (n <= 1) { return 1 } return memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo) }
在上面的代碼片段中,我們調(diào)整函數(shù)以接受一個(gè)可選參數(shù) memo
。我們使用 memo
對(duì)象作為緩存來存儲(chǔ)斐波那契數(shù)列,并將其各自的索引作為鍵,以便在執(zhí)行過程中稍后需要時(shí)檢索它們。
memo = memo || {}
在這里,檢查是否在調(diào)用函數(shù)時(shí)將 memo
作為參數(shù)接收。如果有,則初始化它以供使用;如果沒有,則將其設(shè)置為空對(duì)象。
if (memo[n]) { return memo[n] }
接下來,檢查當(dāng)前鍵 n
是否有緩存值,如果有,則返回其值。
和之前的解一樣,我們指定了 n
小于等于 1
時(shí)的終止遞歸。
最后,我們遞歸地調(diào)用n值較小的函數(shù),同時(shí)將緩存值(memo
)傳遞給每個(gè)函數(shù),以便在計(jì)算期間使用。這確保了在以前計(jì)算并緩存值時(shí),我們不會(huì)第二次執(zhí)行如此昂貴的計(jì)算。我們只是從 memo
中取回值。
注意,我們?cè)诜祷鼐彺嬷皩⒆罱K結(jié)果添加到緩存中。
使用 JSPerf 測(cè)試性能
可以使用些鏈接來性能測(cè)試。在那里,我們運(yùn)行一個(gè)測(cè)試來評(píng)估使用這兩種方法執(zhí)行fibonacci(20)
所需的時(shí)間。結(jié)果如下:
哇! ! !這讓人很驚訝,使用緩存的 fibonacci 函數(shù)是最快的。然而,這一數(shù)字相當(dāng)驚人。它執(zhí)行 126,762 ops/sec,這遠(yuǎn)遠(yuǎn)大于執(zhí)行 1,751 ops/sec
的純遞歸解決方案,并且比較沒有緩存的遞歸速度大約快 99%。
注:“ops/sec”表示每秒的操作次數(shù),就是一秒鐘內(nèi)預(yù)計(jì)要執(zhí)行的測(cè)試次數(shù)。
現(xiàn)在我們已經(jīng)看到了緩存在函數(shù)級(jí)別上對(duì)應(yīng)用程序的性能有多大的影響。這是否意味著對(duì)于應(yīng)用程序中的每個(gè)昂貴函數(shù),我們都必須創(chuàng)建一個(gè)修改后的變量來維護(hù)內(nèi)部緩存?
不,回想一下,我們通過從函數(shù)返回函數(shù)來了解到,即使在外部執(zhí)行它們,它們也會(huì)導(dǎo)致它們繼承父函數(shù)的范圍,這使得可以將某些特征和屬性從封閉函數(shù)傳遞到返回的函數(shù)。
使用函數(shù)的方式
在下面的代碼片段中,我們創(chuàng)建了一個(gè)高階的函數(shù) memoizer
。有了這個(gè)函數(shù),將能夠輕松地將緩存應(yīng)用到任何函數(shù)。
function memoizer(fun) { let cache = {} return function (n) { if (cache[n] != undefined) { return cache[n] } else { let result = fun(n) cache[n] = result return result } } }
上面,我們簡(jiǎn)單地創(chuàng)建一個(gè)名為 memoizer
的新函數(shù),它接受將函數(shù) fun
作為參數(shù)進(jìn)行緩存。在函數(shù)中,我們創(chuàng)建一個(gè)緩存對(duì)象來存儲(chǔ)函數(shù)執(zhí)行的結(jié)果,以便將來使用。
從 memoizer
函數(shù)中,我們返回一個(gè)新函數(shù),根據(jù)上面討論的閉包原則,這個(gè)函數(shù)無論在哪里執(zhí)行都可以訪問 cache
。
在返回的函數(shù)中,我們使用 if..else
語句檢查是否已經(jīng)有指定鍵(參數(shù)) n
的緩存值。如果有,則取出并返回它。如果沒有,我們使用函數(shù)來計(jì)算結(jié)果,以便緩存。然后,我們使用適當(dāng)?shù)逆I n
將結(jié)果添加到緩存中,以便以后可以從那里訪問它。最后,我們返回了計(jì)算結(jié)果。
很順利!
要將 memoizer 函數(shù)應(yīng)用于最初遞歸的 fibonacci
函數(shù),我們調(diào)用 memoizer
函數(shù),將 fibonacci
函數(shù)作為參數(shù)傳遞進(jìn)去。
const fibonacciMemoFunction = memoizer(fibonacciRecursive)
測(cè)試 memoizer 函數(shù)
當(dāng)我們將 memoizer
函數(shù)與上面的例子進(jìn)行比較時(shí),結(jié)果如下:
memoizer
函數(shù)以 42,982,762 ops/sec 的速度提供了最快的解決方案,比之前考慮的解決方案速度要快 100%。
關(guān)于緩存,我們已經(jīng)說明什么是緩存 、為什么要有緩存和如何實(shí)現(xiàn)緩存?,F(xiàn)在我們來看看什么時(shí)候使用緩存。
何時(shí)使用緩存
當(dāng)然,使用緩存效率是級(jí)高的,你現(xiàn)在可能想要緩存所有的函數(shù),這可能會(huì)變得非常無益。以下幾種情況下,適合使用緩存:
緩存庫
總結(jié)
使用緩存方法 ,我們可以防止函數(shù)調(diào)用函數(shù)來反復(fù)計(jì)算相同的結(jié)果,現(xiàn)在是你把這些知識(shí)付諸實(shí)踐的時(shí)候了。
以上是JavaScript緩存的案例分析的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。