成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript專題之函數(shù)記憶

RobinTang / 2193人閱讀

摘要:專題系列第十七篇,講解函數(shù)記憶與菲波那切數(shù)列的實(shí)現(xiàn)定義函數(shù)記憶是指將上次的計(jì)算結(jié)果緩存起來(lái),當(dāng)下次調(diào)用時(shí),如果遇到相同的參數(shù),就直接返回緩存中的數(shù)據(jù)。

JavaScript 專題系列第十七篇,講解函數(shù)記憶與菲波那切數(shù)列的實(shí)現(xiàn)

定義

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

舉個(gè)例子:

function add(a, b) {
    return a + b;
}

// 假設(shè) memorize 可以實(shí)現(xiàn)函數(shù)記憶
var memoizedAdd = memorize(add);

memoizedAdd(1, 2) // 3
memoizedAdd(1, 2) // 相同的參數(shù),第二次調(diào)用時(shí),從緩存中取出數(shù)據(jù),而非重新計(jì)算一次
原理

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

第一版

我們來(lái)寫一版:

// 第一版 (來(lái)自《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)
    }
}

我們來(lái)測(cè)試一下:

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 大約耗時(shí) 60ms,如果我們不使用函數(shù)記憶,大約耗時(shí) 1.3 ms 左右。

注意

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

所以,函數(shù)記憶也并不是萬(wàn)能的,你看這個(gè)簡(jiǎn)單的場(chǎng)景,其實(shí)并不適合用函數(shù)記憶。

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

第二版

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

var propValue = function(obj){
    return obj.value
}

var memoizedAdd = memorize(propValue)

console.log(memoizedAdd({value: 1})) // 1
console.log(memoizedAdd({value: 2})) // 1

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

// 第二版 (來(lái)自 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;
};

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

var add = function(a, b, c) {
  return a + b + c
}

var memoizedAdd = memorize(add)

memoizedAdd(1, 2, 3) // 6
memoizedAdd(1, 2, 4) // 6

肯定是有問(wèn)題的,如果要支持多參數(shù),我們就需要傳入 hasher 函數(shù),自定義存儲(chǔ)的 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)) // 6
console.log(memoizedAdd(1, 2, 4)) // 7

如果使用 JSON.stringify,參數(shù)是對(duì)象的問(wèn)題也可以得到解決,因?yàn)榇鎯?chǔ)的是對(duì)象序列化后的字符串。

適用場(chǎng)景

我們以斐波那契數(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

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

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

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

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

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

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

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

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

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

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

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

當(dāng)執(zhí)行 fib(10) 時(shí),相當(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

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

興奮的同時(shí)不要忘記思考:為什么會(huì)是 12 次呢?

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

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

多說(shuō)一句

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

專題系列

JavaScript專題系列目錄地址:https://github.com/mqyqingfeng/Blog。

JavaScript專題系列預(yù)計(jì)寫二十篇左右,主要研究日常開(kāi)發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖、節(jié)流、去重、類型判斷、拷貝、最值、扁平、柯里、遞歸、亂序、排序等,特點(diǎn)是研(chao)究(xi) underscore 和 jQuery 的實(shí)現(xiàn)方式。

如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤?,?qǐng)務(wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎 star,對(duì)作者也是一種鼓勵(lì)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/88389.html

相關(guān)文章

  • JavaScript專題系列文章

    摘要:專題系列共計(jì)篇,主要研究日常開(kāi)發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖節(jié)流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點(diǎn)是研究專題之函數(shù)組合專題系列第十六篇,講解函數(shù)組合,并且使用柯里化和函數(shù)組合實(shí)現(xiàn)模式需求我們需要寫一個(gè)函數(shù),輸入,返回。 JavaScript 專題之從零實(shí)現(xiàn) jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實(shí)現(xiàn)一個(gè) jQuery 的 ext...

    Maxiye 評(píng)論0 收藏0
  • JavaScript專題系列20篇正式完結(jié)!

    摘要:寫在前面專題系列是我寫的第二個(gè)系列,第一個(gè)系列是深入系列。專題系列自月日發(fā)布第一篇文章,到月日發(fā)布最后一篇,感謝各位朋友的收藏點(diǎn)贊,鼓勵(lì)指正。 寫在前面 JavaScript 專題系列是我寫的第二個(gè)系列,第一個(gè)系列是 JavaScript 深入系列。 JavaScript 專題系列共計(jì) 20 篇,主要研究日常開(kāi)發(fā)中一些功能點(diǎn)的實(shí)現(xiàn),比如防抖、節(jié)流、去重、類型判斷、拷貝、最值、扁平、柯里...

    sixleaves 評(píng)論0 收藏0
  • JavaScript專題遞歸

    摘要:專題系列第十八篇,講解遞歸和尾遞歸定義程序調(diào)用自身的編程技巧稱為遞歸。然而非尾調(diào)用函數(shù),就會(huì)創(chuàng)建多個(gè)執(zhí)行上下文壓入執(zhí)行上下文棧。所以我們只用把階乘函數(shù)改造成一個(gè)尾遞歸形式,就可以避免創(chuàng)建那么多的執(zhí)行上下文。 JavaScript 專題系列第十八篇,講解遞歸和尾遞歸 定義 程序調(diào)用自身的編程技巧稱為遞歸(recursion)。 階乘 以階乘為例: function factorial(n...

    asoren 評(píng)論0 收藏0
  • JS專題memoization

    摘要:前言在計(jì)算機(jī)領(lǐng)域,記憶是主要用于加速程序計(jì)算的一種優(yōu)化技術(shù),它使得函數(shù)避免重復(fù)演算之前已被處理過(guò)的輸入,而返回已緩存的結(jié)果。被執(zhí)行了不是素?cái)?shù),其他數(shù)字默認(rèn)是素?cái)?shù)。我們可以看出,如果從開(kāi)始打印斐波那契數(shù)列,函數(shù)被執(zhí)行了次。 前言 在計(jì)算機(jī)領(lǐng)域,記憶(memoization)是主要用于加速程序計(jì)算的一種優(yōu)化技術(shù),它使得函數(shù)避免重復(fù)演算之前已被處理過(guò)的輸入,而返回已緩存的結(jié)果。 -- wi...

    zhisheng 評(píng)論0 收藏0
  • 前端_JavaScript

    摘要:為此決定自研一個(gè)富文本編輯器。例如當(dāng)要轉(zhuǎn)化的對(duì)象有環(huán)存在時(shí)子節(jié)點(diǎn)屬性賦值了父節(jié)點(diǎn)的引用,為了關(guān)于函數(shù)式編程的思考作者李英杰,美團(tuán)金融前端團(tuán)隊(duì)成員。只有正確使用作用域,才能使用優(yōu)秀的設(shè)計(jì)模式,幫助你規(guī)避副作用。 JavaScript 專題之惰性函數(shù) JavaScript 專題系列第十五篇,講解惰性函數(shù) 需求 我們現(xiàn)在需要寫一個(gè) foo 函數(shù),這個(gè)函數(shù)返回首次調(diào)用時(shí)的 Date 對(duì)象,注意...

    Benedict Evans 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<