自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Web性能優(yōu)化:理解及使用JavaScript緩存

開發(fā) 前端
隨著我們的應(yīng)用程序的不斷增長并開始進(jìn)行復(fù)雜的計算時,對速度的需求越來越高,所以流程的優(yōu)化變得必不可少。 當(dāng)我們忽略這個問題時,我們最終的程序需要花費大量時間并在執(zhí)行期間消耗大量的系統(tǒng)資源。

 [[260205]]

隨著我們的應(yīng)用程序的不斷增長并開始進(jìn)行復(fù)雜的計算時,對速度的需求越來越高(🏎️),所以流程的優(yōu)化變得必不可少。 當(dāng)我們忽略這個問題時,我們最終的程序需要花費大量時間并在執(zhí)行期間消耗大量的系統(tǒng)資源。

緩存是一種優(yōu)化技術(shù),通過存儲開銷大的函數(shù)執(zhí)行的結(jié)果,并在相同的輸入再次出現(xiàn)時返回已緩存的結(jié)果,從而加快應(yīng)用程序的速度。

如果這對你沒有多大意義,那沒關(guān)系。 本文深入解釋了為什么需要進(jìn)行緩存,緩存是什么,如何實現(xiàn)以及何時應(yīng)該使用緩存。

什么是緩存

緩存是一種優(yōu)化技術(shù),通過存儲開銷大的函數(shù)執(zhí)行的結(jié)果,并在相同的輸入再次出現(xiàn)時返回已緩存的結(jié)果,從而加快應(yīng)用程序的速度。

在這一點上,我們很清楚,緩存的目的是減少執(zhí)行“昂貴的函數(shù)調(diào)用”所花費的時間和資源。

什么是昂貴的函數(shù)調(diào)用?別搞混了,我們不是在這里花錢。在計算機(jī)程序的上下文中,我們擁有的兩種主要資源是時間和內(nèi)存。因此,一個昂貴的函數(shù)調(diào)用是指一個函數(shù)調(diào)用中,由于計算量大,在執(zhí)行過程中大量占用了計算機(jī)的資源和時間。

然而,就像對待金錢一樣,我們需要節(jié)約。為此,使用緩存來存儲函數(shù)調(diào)用的結(jié)果,以便在將來的時間內(nèi)快速方便地訪問。

緩存只是一個臨時的數(shù)據(jù)存儲,它保存數(shù)據(jù),以便將來對該數(shù)據(jù)的請求能夠更快地得到處理。

因此,當(dāng)一個昂貴的函數(shù)被調(diào)用一次時,結(jié)果被存儲在緩存中,這樣,每當(dāng)在應(yīng)用程序中再次調(diào)用該函數(shù)時,結(jié)果就會從緩存中非??焖俚厝〕觯恍枰匦逻M(jìn)行任何計算。

為什么緩存很重要?

下面是一個實例,說明了緩存的重要性:

想象一下,你正在公園里讀一本封面很吸引人的新小說。每次一個人經(jīng)過,他們都會被封面吸引,所以他們會問書名和作者。***次被問到這個問題的時候,你翻開書,讀出書名和作者的名字?,F(xiàn)在越來越多的人來這里問同樣的問題。你是一個很好的人🙂,所以你回答所有問題。

你會翻開封面,把書名和作者的名字一一告訴他,還是開始憑記憶回答?哪個能節(jié)省你更多的時間?

發(fā)現(xiàn)其中的相似之處了嗎?使用記憶法,當(dāng)函數(shù)提供輸入時,它執(zhí)行所需的計算并在返回值之前將結(jié)果存儲到緩存中。如果將來接收到相同的輸入,它就不必一遍又一遍地重復(fù),它只需要從緩存(內(nèi)存)中提供答案。

緩存是怎么工作的

JavaScript 中的緩存的概念主要建立在兩個概念之上,它們分別是:

  •  閉包
  •  高階函數(shù)(返回函數(shù)的函數(shù))

閉包

閉包是函數(shù)和聲明該函數(shù)的詞法環(huán)境的組合。

不是很清楚? 我也這么認(rèn)為。

為了更好的理解,讓我們快速研究一下 JavaScript 中詞法作用域的概念,詞法作用域只是指程序員在編寫代碼時指定的變量和塊的物理位置。如下代碼: 

  1. function foo(a) {  
  2.   var b = a + 2;  
  3.   function bar(c) {  
  4.     console.log(a, b, c);  
  5.   }  
  6.   bar(b * 2);  
  7.  
  8. foo(3); // 3, 5, 10 

從這段代碼中,我們可以確定三個作用域:

  •  全局作用域(包含 foo 作為唯一標(biāo)識符)
  •  foo 作用域,它有標(biāo)識符 a、b 和 bar
  •  bar 作用域,包含 c 標(biāo)識符

仔細(xì)查看上面的代碼,我們注意到函數(shù) foo 可以訪問變量 a 和 b,因為它嵌套在 foo 中。注意,我們成功地存儲了函數(shù) bar 及其運行環(huán)境。因此,我們說 bar 在 foo 的作用域上有一個閉包。

你可以在遺傳的背景下理解這一點,即個體有機(jī)會獲得并表現(xiàn)出遺傳特征,即使是在他們當(dāng)前的環(huán)境之外,這個邏輯突出了閉包的另一個因素,引出了我們的第二個主要概念。

從函數(shù)返回函數(shù)

通過接受其他函數(shù)作為參數(shù)或返回其他函數(shù)的函數(shù)稱為高階函數(shù)。

閉包允許我們在封閉函數(shù)的外部調(diào)用內(nèi)部函數(shù),同時保持對封閉函數(shù)的詞法作用域的訪問

讓我們對前面的示例中的代碼進(jìn)行一些調(diào)整,以解釋這一點。 

  1. function foo(){  
  2.   var a = 2 
  3.   function bar() {  
  4.     console.log(a);  
  5.   }  
  6.   return bar;  
  7.  
  8. var baz = foo();  
  9. baz();//2 

注意函數(shù) foo 如何返回另一個函數(shù) bar。這里我們執(zhí)行函數(shù) foo 并將返回值賦給baz。但是在本例中,我們有一個返回函數(shù),因此,baz 現(xiàn)在持有對 foo 中定義的bar 函數(shù)的引用。

最有趣的是,當(dāng)我們在 foo 的詞法作用域之外執(zhí)行函數(shù) baz 時,仍然會得到 a 的值,這怎么可能呢?😕

請記住,由于閉包的存在,bar 總是可以訪問 foo 中的變量(繼承的特性),即使它是在 foo 的作用域之外執(zhí)行的。

案例研究:斐波那契數(shù)列

斐波那契數(shù)列是什么?

斐波那契數(shù)列是一組數(shù)字,以1 或 0 開頭,后面跟著1,然后根據(jù)每個數(shù)字等于前兩個數(shù)字之和規(guī)則進(jìn)行。如 

  1. 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, … 

或者 

  1. 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, … 

挑戰(zhàn):編寫一個函數(shù)返回斐波那契數(shù)列中的 n 元素,其中的序列是: 

  1. [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …] 

知道每個值都是前兩個值的和,這個問題的遞歸解是: 

  1. function fibonacci(n) {  
  2.   if (n <= 1) {  
  3.     return 1  
  4.   }  
  5.   return fibonacci(n - 1) + fibonacci(n - 2)  

確實簡潔準(zhǔn)確!但是,有一個問題。請注意,當(dāng) n 的值到終止遞歸之前,需要做大量的工作和時間,因為序列中存在對某些值的重復(fù)求值。

看看下面的圖表,當(dāng)我們試圖計算 fib(5)時,我們注意到我們反復(fù)地嘗試在不同分支的下標(biāo) 0,1,2,3 處找到 Fibonacci 數(shù),這就是所謂的冗余計算,而這正是緩存所要消除的。

 

  1. function fibonacci(n, memo) {  
  2.   memomemo = memo || {}  
  3.   if (memo[n]) {  
  4.     return memo[n]  
  5.   }  
  6.   if (n <= 1) {  
  7.     return 1  
  8.   }  
  9.   return memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)  

在上面的代碼片段中,我們調(diào)整函數(shù)以接受一個可選參數(shù) memo。我們使用 memo 對象作為緩存來存儲斐波那契數(shù)列,并將其各自的索引作為鍵,以便在執(zhí)行過程中稍后需要時檢索它們。 

  1. memomemo = memo || {} 

在這里,檢查是否在調(diào)用函數(shù)時將 memo 作為參數(shù)接收。如果有,則初始化它以供使用;如果沒有,則將其設(shè)置為空對象。 

  1. if (memo[n]) {  
  2.   return memo[n]  

接下來,檢查當(dāng)前鍵 n 是否有緩存值,如果有,則返回其值。

和之前的解一樣,我們指定了 n 小于等于 1 時的終止遞歸。

***,我們遞歸地調(diào)用n值較小的函數(shù),同時將緩存值(memo)傳遞給每個函數(shù),以便在計算期間使用。這確保了在以前計算并緩存值時,我們不會第二次執(zhí)行如此昂貴的計算。我們只是從 memo 中取回值。

注意,我們在返回緩存之前將最終結(jié)果添加到緩存中。

使用 JSPerf 測試性能

可以使用些鏈接來性能測試。在那里,我們運行一個測試來評估使用這兩種方法執(zhí)行fibonacci(20) 所需的時間。結(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ù)計要執(zhí)行的測試次數(shù)。

現(xiàn)在我們已經(jīng)看到了緩存在函數(shù)級別上對應(yīng)用程序的性能有多大的影響。這是否意味著對于應(yīng)用程序中的每個昂貴函數(shù),我們都必須創(chuàng)建一個修改后的變量來維護(hù)內(nèi)部緩存?

不,回想一下,我們通過從函數(shù)返回函數(shù)來了解到,即使在外部執(zhí)行它們,它們也會導(dǎo)致它們繼承父函數(shù)的范圍,這使得可以將某些特征和屬性從封閉函數(shù)傳遞到返回的函數(shù)。

使用函數(shù)的方式

在下面的代碼片段中,我們創(chuàng)建了一個高階的函數(shù) memoizer。有了這個函數(shù),將能夠輕松地將緩存應(yīng)用到任何函數(shù)。 

  1. function memoizer(fun) {  
  2.   let cache = {}  
  3.   return function (n) {  
  4.     if (cache[n] != undefined) {  
  5.       return cache[n]  
  6.     } else {  
  7.       let result = fun(n)  
  8.       cache[n] = result 
  9.        return result  
  10.     }  
  11.   }  

上面,我們簡單地創(chuàng)建一個名為 memoizer 的新函數(shù),它接受將函數(shù) fun 作為參數(shù)進(jìn)行緩存。在函數(shù)中,我們創(chuàng)建一個緩存對象來存儲函數(shù)執(zhí)行的結(jié)果,以便將來使用。

從 memoizer 函數(shù)中,我們返回一個新函數(shù),根據(jù)上面討論的閉包原則,這個函數(shù)無論在哪里執(zhí)行都可以訪問 cache。

在返回的函數(shù)中,我們使用 if..else 語句檢查是否已經(jīng)有指定鍵(參數(shù)) n 的緩存值。如果有,則取出并返回它。如果沒有,我們使用函數(shù)來計算結(jié)果,以便緩存。然后,我們使用適當(dāng)?shù)逆I n 將結(jié)果添加到緩存中,以便以后可以從那里訪問它。***,我們返回了計算結(jié)果。

很順利!

要將 memoizer 函數(shù)應(yīng)用于最初遞歸的 fibonacci 函數(shù),我們調(diào)用 memoizer 函數(shù),將 fibonacci 函數(shù)作為參數(shù)傳遞進(jìn)去。 

  1. const fibonacciMemoFunction = memoizer(fibonacciRecursive) 

測試 memoizer 函數(shù)

當(dāng)我們將 memoizer 函數(shù)與上面的例子進(jìn)行比較時,結(jié)果如下:

memoizer 函數(shù)以 42,982,762 ops/sec 的速度提供了最快的解決方案,比之前考慮的解決方案速度要快 100%。

關(guān)于緩存,我們已經(jīng)說明什么是緩存 、為什么要有緩存和如何實現(xiàn)緩存。現(xiàn)在我們來看看什么時候使用緩存。

何時使用緩存

當(dāng)然,使用緩存效率是級高的,你現(xiàn)在可能想要緩存所有的函數(shù),這可能會變得非常無益。以下幾種情況下,適合使用緩存:

  •  對于昂貴的函數(shù)調(diào)用,執(zhí)行復(fù)雜計算的函數(shù)。
  •  對于具有有限且高度重復(fù)輸入范圍的函數(shù)。
  •  用于具有重復(fù)輸入值的遞歸函數(shù)。
  •  對于純函數(shù),即每次使用特定輸入調(diào)用時返回相同輸出的函數(shù)。

緩存庫

  •  Lodash
  •  Memoizer
  •  Fastmemoize
  •  Moize
  •  Reselect for Redux

總結(jié)

使用緩存方法 ,我們可以防止函數(shù)調(diào)用函數(shù)來反復(fù)計算相同的結(jié)果,現(xiàn)在是你把這些知識付諸實踐的時候了。

 

責(zé)任編輯:龐桂玉 來源: segmentfault
相關(guān)推薦

2010-05-28 10:23:59

JavaScriptWeb

2019-03-14 15:38:19

ReactJavascript前端

2014-12-10 10:12:02

Web

2017-01-06 08:51:31

2015-12-16 12:40:32

H5緩存機(jī)制移動

2022-03-02 11:13:50

Web前端開發(fā)

2019-03-05 10:20:49

WebWebpack分離數(shù)據(jù)

2015-06-23 16:36:11

Web性能優(yōu)化

2012-01-10 16:22:25

Web

2015-08-17 10:35:56

Web性能優(yōu)化

2013-01-22 15:27:23

WebWeb前端

2025-03-28 08:35:00

2025-02-04 10:58:16

2009-06-10 22:00:57

JavaScript腳

2009-06-11 17:15:23

JavaScript性

2012-12-24 09:55:15

JavaJava WebJava優(yōu)化

2015-09-15 10:54:54

HTTP2 WEB 性能優(yōu)化

2022-08-01 14:59:57

Web前端后端

2014-03-19 14:34:06

JQuery高性能

2015-09-15 10:40:26

HTTP2 WEB 性能優(yōu)化
點贊
收藏

51CTO技術(shù)棧公眾號