Javascript緩存函數&柯里化&偏函數

緩存函數

memorizition

定義:將上次的計算結果緩存起來,當下次調用時,若是遇到相同的參數,就直接返回緩存中的數據。javascript

let add = (a,b) => a+b;
let calc = memoize(add);
calc(10,20);//30
calc(10,20);//30 緩存

若是要實現以上功能,主要依靠 閉包 、柯里化、高階函數 html

實現原理:把參數和對應的結果數據存在一個對象中,調用時判斷參數對應的數據是否存在,存在就返回對應的結果數據,不然就返回計算結果。java

理論有了,咱們來實現一個緩存函數:編程

let memoize = function (func, content) {
  let cache = Object.create(null)
  content = content || this
  return (...key) => {
    if (!cache[key]) {
      cache[key] = func.apply(content, key)
    }
    return cache[key]
  }
}

過程分析:api

  • 在當前函數做用域定義了一個空對象,用於緩存運行結果
  • 運用柯里化返回一個函數,返回的函數由於做用域鏈的緣由,能夠訪問到cache
  • 而後判斷輸入參數是否是在cache的中。若是已經存在,直接返回cache的內容,若是沒有存在,使用函數func對輸入參數求值,而後把結果存儲在cache中。

在Vue中也有所體現數組

/**
 * Create a cached version of a pure function.
 */
function cached (fn) {
  var cache = Object.create(null);
  return (function cachedFn (str) {
    var hit = cache[str];
    return hit || (cache[str] = fn(str))
  })
}

/**
 * Capitalize a string.
 */
var capitalize = cached(function (str) {
  return str.charAt(0).toUpperCase() + str.slice(1)
});

...

capitalize(camelizedId)

適用場景:緩存

  • 須要大量重複計算
  • 大量計算而且依賴以前的結果

curry與偏函數

curry

function currying 把接受多個參數的函數轉換成接受一個單一參數的函數閉包

// 非函數柯里化
var add = function (x,y) {
    return x+y;
}
add(3,4) //7

// 函數柯里化
var add2 = function (x) {
    //**返回函數**
    return function (y) {
        return x+y;
    }
}
add2(3)(4) //7

在上面的例子中,咱們將多維參數的函數拆分,先接受第一個函數,而後返回一個新函數,用於接收後續參數。app

就此,咱們得出一個初步的結論:柯里化後的函數,若是形參個數等於實參個數,返回函數執行結果,否者,返回一個柯里化函數。函數式編程

經過柯里化可實現代碼複用,使用函數式編程。

實現柯里化函數

從上面例子中,咱們定義了有兩個形參的函數,爲了實現柯里化,函數傳入第一個形參後返回一個函數用來接收第二個形參。那麼若是咱們的定義的形參有個,那麼也就須要嵌套2層,分別處理後兩個參數,如

var add3 = function (x) {
    return function (y) {
        return function (z) {
            return x + y + z;
        }
    }
}
add3(1)(3)(5)

若是形參有5個,7個呢?這裏咱們使用遞歸,進行簡化。不知有沒有看到規律,形參的個數決定了函數的嵌套層數。 即 有n個參數就得嵌套n-1個函數 ,那咱們來改造一番。

// 通用型柯里化
function currying (fn) {
    // 未柯里化函數所需的參數個數  https://www.cnblogs.com/go4it/p/9678028.html
    var limit = fn.length; 
    var params = []; // 存儲遞歸過程的全部參數,用於遞歸出口計算值
    return function _curry(...args) { 
        params = params.concat(args); // 收集遞歸參數
        if (limit <= params.length) {
            let tempParams=params.slice(0,limit)
            if(limit===params.length){ //參數個數知足時清除已緩存的參數
                params=[]
            }
            // 返回函數執行結果
            return fn.apply(null, params);
        } else {
            // 返回一個柯里化函數
            return _curry;
        }
    };
}
function add(x,y,z){
    return x + y+z;
}
// 函數柯里化
var addCurried=currying(add);
console.log(`addCurried(1)(2)(3)`,addCurried(1)(2)(3))//6
console.log(`addCurried(3,3,3)`,addCurried(3,3,3))//9
console.log(`addCurried(1,2)(3)`,addCurried(1,2)(3))//6
console.log(`addCurried(3)(4,5)`,addCurried(3)(4,5))//12

咱們看看addCurried(1)(2)(3)中發生了什麼:

  1. 首先調用`addCurried(1),將1保存在詞法環境中,而後遞歸調用_curry繼續收集後續參數
  2. addCurried(1)(2),參數2與第一次的參數1,合併調用,因未達到形參個數要求,繼續遞歸返回_curry
  3. 調用addCurried(1)(2)(3),參數爲3,在接下去的調用中,與1,2進行合併,傳入原函數add

注意點

  1. 柯里化基於閉包實現,可能會致使內存泄露
  2. 使用遞歸,執行會下降性能,遞歸多時會發生棧溢出,須要進行遞歸優化,參考
  3. arguments是類數組,使用Array.prototype.slice.call轉換爲數組時,效率低。

偏函數

簡單描述,就是把一個函數的某些參數先固化,也就是設置默認值,返回一個新的函數,在新函數中繼續接收剩餘參數,這樣調用這個新函數會更簡單。

// 乘法
let multi = (x,y) => x * y;
// 構造一個對數值乘以2的函數
let double = multi.bind(null,2);
console.log(double(3));//6
console.log(double(5));//10

在這個例子中,咱們使用bind 固定了 乘數,返回一個函數。該函數接受一個參數做爲 被乘數。--將部分參數固定,只對剩餘參數進行計算。

基於以上推導,咱們來實現一個無綁定上下文的偏函數:

/**
 * 偏函數實現
 * @param func 應用函數
 * @param argsBound 固定參數
 * @return {function(...[*]): *}
 */
let partial = (func, ...argsBound) => {
  if (typeof func !== 'function') throw new TypeError(
    `${typeof func} is not a function`)
  return function (...args) { // (*)
    if(func.length-argsBound.length>args.length) throw new Error(`miss arguments`)
    return func.call(this, ...argsBound.concat(...args))
  }
}
let partialMulti= partial(multi,2)
console.log(partialMulti());//Error: miss arguments
console.log(partialMulti(3));//6

partial(func[, arg1, arg2...]) 調用的結果是一個基於 func 的封裝函數,以及:

  • 和它傳入的函數一致的 this
  • 而後傳入 ...argsBound —— 來自偏函數調用傳入的參數
  • 而後傳入 ...args —— 傳入封裝函數的參數

區別

偏函數與柯里化很類似,下面咱們作個對比:

柯里化:將一個對參數函數轉換成多個單參數的函數,也就是將一個n元函數轉換爲n個一元函數。

偏函數:固定一個函數的一個或多個參數,也就是將一個n元函數轉換成一個n-x元函數。

我的理解:偏函數是柯里化的一種特定的應用場景

使用場景

  • 動態生成函數
  • 減小參數
  • 延遲計算
相關文章
相關標籤/搜索