JS專題系列之惰性函數(shù)與記憶函數(shù)

一、惰性函數(shù)

惰性函數(shù):顧名思義就是懶惰的函數(shù),舉一個(gè)簡(jiǎn)單的例子,如果我們需要判斷當(dāng)前瀏覽器是哪一種瀏覽器,我們需要封裝一個(gè)函數(shù),根據(jù)判斷來進(jìn)行返回瀏覽器類型,但是如果你在同一個(gè)瀏覽器中調(diào)用2次這個(gè)函數(shù),那么這個(gè)函數(shù)中就需要做2次判斷。其實(shí)這是一種無用的性能消耗,因此我們的惰性函數(shù)就登場(chǎng)了

原理:根據(jù)第一次調(diào)用的判斷重寫當(dāng)前函數(shù)

function getStyle(obj,attr){
  if(obj.currentStyle) {
    return obj.currentStyle[attr]
  } else {
    return getComputedStyle(obj,false)[attr];
  }
}

上述方法我們知道用來獲取元素的屬性值,但是問題是我們每次調(diào)用該函數(shù)都要進(jìn)行一次判斷,如果在同一個(gè)瀏覽器中連續(xù)調(diào)用2次,每次都進(jìn)行一次判斷明顯這是無用功,因此我們可以根據(jù)惰性函數(shù)原理進(jìn)行優(yōu)化

function getStyle(obj,attr){
  if(obj.currentStyle) {
    getStyle = function(obj,attr) {
      return obj.currentStyle[attr]
    }
  } else {
    getStyle = function(obj,attr) {
      return getComputedStyle(obj,false)[attr];
    }
  }
  return getStyle(obj,attr);
}

例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        #box{width: 200px;}
        #box1{width:300px;}
    </style>
</head>
<body>
    <div id="box"></div>
    <div id="box1"></div>
</body>
</html>
<script>
     var div = document.getElementsByTagName("div")[0];
     function getStyle(obj,attr){  
        if(obj.currentStyle){
            getStyle = function(obj,attr){
                return obj.currentStyle[attr]
            }
        }else{
            getStyle = function(obj,attr){
                return getComputedStyle(obj,false)[attr]
            }
        }
        return getStyle(obj,attr)
    }
  console.log(getStyle)
  /*
    function getStyle(obj,attr){  
        if(obj.currentStyle){
            getStyle = function(obj,attr){
                return obj.currentStyle[attr]
            }
        }else{
            getStyle = function(obj,attr){
                return getComputedStyle(obj,false)[attr]
            }
        }
        return getStyle(obj,attr)
    }
  */
  console.log(getStyle(box,"width"))
  console.log(getStyle)
  /*
    chrome瀏覽器
    (obj,attr){
            return getComputedStyle(obj,false)[attr]
        }
  */
</script>

二、記憶函數(shù)

如果熟悉Vue的同學(xué)應(yīng)該知道vue中有一個(gè)屬性叫做computed它可以緩存計(jì)算后的結(jié)果。記憶函數(shù)的功能也類似于computed。它可以將上次的結(jié)果緩存起來,當(dāng)下次調(diào)用的時(shí)候,如果傳遞的參數(shù)相同,則直接返回緩存中的數(shù)據(jù)

舉個(gè)例子:

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

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

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

原理

我們只需要把參數(shù)和對(duì)應(yīng)的結(jié)果存到一個(gè)對(duì)象中,調(diào)用時(shí),判斷參數(shù)對(duì)應(yīng)的數(shù)據(jù)是否存在,如果存在則直接返回對(duì)應(yīng)的結(jié)果

第一版

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)
        }
    }
}

// 調(diào)用
var add = function(a, b, c) {
  return a + b + c
}
var memoizedAdd = memoize(add)
memoizedAdd(1, 2, 3) // 5

因?yàn)榈谝话媸褂昧?code>join方法,我們很容易想到當(dāng)參數(shù)是對(duì)象的時(shí)候,就會(huì)自動(dòng)調(diào)用 toString方法轉(zhuǎn)換成 [Object object],再拼接字符串作為 key 值。這樣就很容易造成key值相同

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

var memoizedAdd = memoize(propValue)

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

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

// 第二版 (來自 underscore 的實(shí)現(xiàn))
var memoize = 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;
};

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

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

var memoizedAdd = memoize(add)

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

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

var memoizedAdd = memoize(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

總結(jié):

使用場(chǎng)景:當(dāng)有需要大量重復(fù)計(jì)算或者計(jì)算依賴之前的結(jié)果的函數(shù),我們都可以使用記憶函數(shù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容