国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

JavaScript學(xué)習(xí)筆記之函數(shù)記憶

2019-11-19 15:31:53
字體:
供稿:網(wǎng)友

本文講解函數(shù)記憶與菲波那切數(shù)列的實(shí)現(xiàn),分享給大家,具體如下

定義

函數(shù)記憶是指將上次的計(jì)算結(jié)果緩存起來,當(dāng)下次調(diào)用時,如果遇到相同的參數(shù),就直接返回緩存中的數(shù)據(jù)。

舉個例子:

function add(a, b) {  return a + b;}// 假設(shè) memorize 可以實(shí)現(xiàn)函數(shù)記憶var memoizedAdd = memorize(add);memoizedAdd(1, 2) // 3memoizedAdd(1, 2) // 相同的參數(shù),第二次調(diào)用時,從緩存中取出數(shù)據(jù),而非重新計(jì)算一次

原理

實(shí)現(xiàn)這樣一個 memorize 函數(shù)很簡單,原理上只用把參數(shù)和對應(yīng)的結(jié)果數(shù)據(jù)存到一個對象中,調(diào)用時,判斷參數(shù)對應(yīng)的數(shù)據(jù)是否存在,存在就返回對應(yīng)的結(jié)果數(shù)據(jù)。

第一版

我們來寫一版:

// 第一版 (來自《JavaScript權(quán)威指南》)function memoize(f) {  var cache = {};  return function(){    var key = arguments.length + Array.prototype.join.call(arguments, ",");    if (key in cache) {      return cache[key]    }    else return cache[key] = f.apply(this, arguments)  }}

我們來測試一下:

var add = function(a, b, c) { return a + b + c}var memoizedAdd = memorize(add)console.time('use memorize')for(var i = 0; i < 100000; i++) {  memoizedAdd(1, 2, 3)}console.timeEnd('use memorize')console.time('not use memorize')for(var i = 0; i < 100000; i++) {  add(1, 2, 3)}console.timeEnd('not use memorize')

在 Chrome 中,使用 memorize 大約耗時 60ms,如果我們不使用函數(shù)記憶,大約耗時 1.3 ms 左右。

注意

什么,我們使用了看似高大上的函數(shù)記憶,結(jié)果卻更加耗時,這個例子近乎有 60 倍呢!

所以,函數(shù)記憶也并不是萬能的,你看這個簡單的場景,其實(shí)并不適合用函數(shù)記憶。

需要注意的是,函數(shù)記憶只是一種編程技巧,本質(zhì)上是犧牲算法的空間復(fù)雜度以換取更優(yōu)的時間復(fù)雜度,在客戶端 JavaScript 中代碼的執(zhí)行時間復(fù)雜度往往成為瓶頸,因此在大多數(shù)場景下,這種犧牲空間換取時間的做法以提升程序執(zhí)行效率的做法是非常可取的。

第二版

因?yàn)榈谝话媸褂昧?join 方法,我們很容易想到當(dāng)參數(shù)是對象的時候,就會自動調(diào)用 toString 方法轉(zhuǎn)換成 [Object object],再拼接字符串作為 key 值。我們寫個 demo 驗(yàn)證一下這個問題:

var propValue = function(obj){  return obj.value}var memoizedAdd = memorize(propValue)console.log(memoizedAdd({value: 1})) // 1console.log(memoizedAdd({value: 2})) // 1

兩者都返回了 1,顯然是有問題的,所以我們看看 underscore 的 memoize 函數(shù)是如何實(shí)現(xiàn)的:

// 第二版 (來自 underscore 的實(shí)現(xiàn))var memorize = function(func, hasher) {  var memoize = function(key) {    var cache = memoize.cache;    var address = '' + (hasher ? hasher.apply(this, arguments) : key);    if (!cache[address]) {      cache[address] = func.apply(this, arguments);    }    return cache[address];  };  memoize.cache = {};  return memoize;};

從這個實(shí)現(xiàn)可以看出,underscore 默認(rèn)使用 function 的第一個參數(shù)作為 key,所以如果直接使用

var add = function(a, b, c) { return a + b + c}var memoizedAdd = memorize(add)memoizedAdd(1, 2, 3) // 6memoizedAdd(1, 2, 4) // 6

肯定是有問題的,如果要支持多參數(shù),我們就需要傳入 hasher 函數(shù),自定義存儲的 key 值。所以我們考慮使用 JSON.stringify:

var memoizedAdd = memorize(add, function(){  var args = Array.prototype.slice.call(arguments)  return JSON.stringify(args)})console.log(memoizedAdd(1, 2, 3)) // 6console.log(memoizedAdd(1, 2, 4)) // 7

如果使用 JSON.stringify,參數(shù)是對象的問題也可以得到解決,因?yàn)榇鎯Φ氖菍ο笮蛄谢蟮淖址?/p>

適用場景

我們以斐波那契數(shù)列為例:

var count = 0;var fibonacci = function(n){  count++;  return n < 2? n : fibonacci(n-1) + fibonacci(n-2);};for (var i = 0; i <= 10; i++){  fibonacci(i)}console.log(count) // 453

我們會發(fā)現(xiàn)最后的 count 數(shù)為 453,也就是說 fibonacci 函數(shù)被調(diào)用了 453 次!也許你會想,我只是循環(huán)到了 10,為什么就被調(diào)用了這么多次,所以我們來具體分析下:

當(dāng)執(zhí)行 fib(0) 時,調(diào)用 1 次

當(dāng)執(zhí)行 fib(1) 時,調(diào)用 1 次

當(dāng)執(zhí)行 fib(2) 時,相當(dāng)于 fib(1) + fib(0) 加上 fib(2) 本身這一次,共 1 + 1 + 1 = 3 次

當(dāng)執(zhí)行 fib(3) 時,相當(dāng)于 fib(2) + fib(1) 加上 fib(3) 本身這一次,共 3 + 1 + 1 = 5 次

當(dāng)執(zhí)行 fib(4) 時,相當(dāng)于 fib(3) + fib(2) 加上 fib(4) 本身這一次,共 5 + 3 + 1 = 9 次

當(dāng)執(zhí)行 fib(5) 時,相當(dāng)于 fib(4) + fib(3) 加上 fib(5) 本身這一次,共 9 + 5 + 1 = 15 次

當(dāng)執(zhí)行 fib(6) 時,相當(dāng)于 fib(5) + fib(4) 加上 fib(6) 本身這一次,共 15 + 9 + 1 = 25 次

當(dāng)執(zhí)行 fib(7) 時,相當(dāng)于 fib(6) + fib(5) 加上 fib(7) 本身這一次,共 25 + 15 + 1 = 41 次

當(dāng)執(zhí)行 fib(8) 時,相當(dāng)于 fib(7) + fib(6) 加上 fib(8) 本身這一次,共 41 + 25 + 1 = 67 次

當(dāng)執(zhí)行 fib(9) 時,相當(dāng)于 fib(8) + fib(7) 加上 fib(9) 本身這一次,共 67 + 41 + 1 = 109 次

當(dāng)執(zhí)行 fib(10) 時,相當(dāng)于 fib(9) + fib(8) 加上 fib(10) 本身這一次,共 109 + 67 + 1 = 177 次
所以執(zhí)行的總次數(shù)為:177 + 109 + 67 + 41 + 25 + 15 + 9 + 5 + 3 + 1 + 1 = 453 次!

如果我們使用函數(shù)記憶呢?

var count = 0;var fibonacci = function(n) {  count++;  return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);};fibonacci = memorize(fibonacci)for (var i = 0; i <= 10; i++) {  fibonacci(i)}console.log(count) // 12

我們會發(fā)現(xiàn)最后的總次數(shù)為 12 次,因?yàn)槭褂昧撕瘮?shù)記憶,調(diào)用次數(shù)從 453 次降低為了 12 次!

興奮的同時不要忘記思考:為什么會是 12 次呢?

從 0 到 10 的結(jié)果各儲存一遍,應(yīng)該是 11 次吶?咦,那多出來的一次是從哪里來的?

所以我們還需要認(rèn)真看下我們的寫法,在我們的寫法中,其實(shí)我們用生成的 fibonacci 函數(shù)覆蓋了原本了 fibonacci 函數(shù),當(dāng)我們執(zhí)行 fibonacci(0) 時,執(zhí)行一次函數(shù),cache 為 {0: 0},但是當(dāng)我們執(zhí)行 fibonacci(2) 的時候,執(zhí)行 fibonacci(1) + fibonacci(0),因?yàn)?fibonacci(0) 的值為 0, !cache[address] 的結(jié)果為 true,又會執(zhí)行一次 fibonacci 函數(shù)。原來,多出來的那一次是在這里!

多說一句

也許你會覺得在日常開發(fā)中又用不到 fibonacci,這個例子感覺實(shí)用價(jià)值不高吶,其實(shí),這個例子是用來表明一種使用的場景,也就是如果需要大量重復(fù)的計(jì)算,或者大量計(jì)算又依賴于之前的結(jié)果,便可以考慮使用函數(shù)記憶。而這種場景,當(dāng)你遇到的時候,你就會知道的。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 盘锦市| 洛浦县| 金坛市| 绥宁县| 福贡县| 临汾市| 塘沽区| 荆门市| 平陆县| 仙居县| 渑池县| 大埔县| 革吉县| 温泉县| 米林县| 玉山县| 莲花县| 九江市| 哈巴河县| 延边| 资阳市| 新昌县| 桓仁| 吉安市| 永德县| 桐乡市| 卓资县| 句容市| 百色市| 收藏| 乌拉特前旗| 中宁县| 泾川县| 金阳县| 大宁县| 鹤峰县| 高雄市| 海城市| 巴彦淖尔市| 西平县| 平果县|