JS__一文學會函數柯里化

最近跟着黃軼老師學習Vue.js 2.5.17-beta版本源碼時,看到了源碼中用了函數柯里化,這裏來寫一篇,記錄一下,加深印象

柯里化是什麼

百度百科上:
在計算機科學中,柯里化(Currying)把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受餘下的參數返回結果的新函數的技術。這個技術由 Christopher Strachey 以邏輯學家 Haskell Curry 命名的,儘管它是 Moses Schnfinkel 和 Gottlob Frege 發明的。

簡單例子實現柯里化

// 普通的add函數
function add(x, y) {
    return x + y
}

// Currying後
function curryingAdd(x) {
    return function (y) {
        return x + y
    }
}

console.log(add(1, 2)) // 3
console.log(curryingAdd(1)(2)) // 3

add函數接受多個參數的函數,柯里化(Currying)後,變成了curryingAdd函數,curryingAdd函數只接受一個單一參數,並且返回接受餘下參數同時返回結果的新函數

這個例子套上百度百科的解釋,就可以比較清楚的知道什麼是柯里化了

存在即合理,爲什麼人們要用柯里化呢?

柯里化好處

參數複用

// 正常正則驗證字符串 reg.test(txt)

// 函數封裝後
function check(reg, txt) {
  return reg.test(txt);
}

check(/\d+/g, "hello world"); // false
check(/[a-z]+/g, "2020-05-11"); // false

// Currying後
function curryingCheck(reg) {
  return function(txt) {
    return reg.test(txt);
  };
}

// 字符串是否有數字
var hasNumber = curryingCheck(/\d+/g);
// 字符串是否有小寫字母
var hasLetter = curryingCheck(/[a-z]+/g);

hasNumber("hello world"); // false
hasNumber("ES6"); // true
hasLetter("2020-05-11"); // false

上面是一個簡單的正則校驗,判斷有沒有包含數字,或者有沒有包括小寫字母;正常直接調用check函數就能滿足;但是假設有很多地方都需要校驗是否包括數字,小寫字母的話,通過柯里化,就可以把check函數第一個參數複用起來

提前返回

待更新

延遲計算/運行

待更新

面試題

// 實現一個add方法,使計算結果能夠滿足如下預期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add() {
  // 第一次執行時,定義一個數組專門用來存儲所有的參數
  let _args = Array.prototype.slice.call(arguments);

  // 在內部聲明一個函數,利用閉包特性 保存_args併合並所有的參數值
  let _adder = function() {
    _args.push(...arguments);
    return _adder;
  };

  // 利用js隱式轉換的特性,當最後執行時隱式轉換,並計算最終的值返回
  _adder.toString = _adder.valueOf = function() {
    return _args.reduce((a, b) => a + b);
  };
  return _adder;
}

add(1)(2)(3); // 6  _args從[1] 變爲[1, 2] 再變爲[1, 2, 3] 最後自動觸發隱式轉換toString()方法 得到數組各項的和6

add(1, 2, 3)(4); // 10
add(1)(2)(3)(4)(5); // 15
add(2, 6)(1); // 9

通用的柯里化封裝方法

function currying(fn, ...rest1) {
  return function(...rest2) {
    return fn.apply(null, rest1.concat(rest2))
  }
}

用它將一個sayHello函數柯里化試試:

function sayHello(name, age, fruit) {
  console.log(console.log(`我叫${name},我${age}歲了,我喜歡吃${fruit}`))
}

const curryingShowMsg1 = currying(sayHello)
curryingShowMsg1('小明', 22, '蘋果')            // 我叫小明,我22歲了,我喜歡吃 蘋果

const curryingShowMsg2 = currying(sayHello, '小愛', 20)
curryingShowMsg2('西瓜') 
// // 我叫小愛,我20歲了,我喜歡吃西瓜

加強版

function curryingHelper(fn, len) {
  const length = len || fn.length  // 第一遍運行length是函數fn一共需要的參數個數,以後是剩餘所需要的參數個數
  return function(...rest) {
    return rest.length >= length    // 檢查是否傳入了fn所需足夠的參數
        ? fn.apply(this, rest)
        : curryingHelper(currying.apply(this, [fn].concat(rest)), length - rest.length)        // 在通用currying函數基礎上
  }
}

function sayHello(name, age, fruit) { console.log(`我叫 ${name},我 ${age} 歲了, 我喜歡吃 ${fruit}`) }    

const betterShowMsg = curryingHelper(sayHello)
betterShowMsg('小陽', 20, '西瓜')      // 我叫 小陽,我 20 歲了, 我喜歡吃 西瓜
betterShowMsg('小豬')(25, '南瓜')      // 我叫 小豬,我 25 歲了, 我喜歡吃 南瓜
betterShowMsg('小明', 22)('倭瓜')      // 我叫 小明,我 22 歲了, 我喜歡吃 倭瓜
betterShowMsg('小愛')(28)('冬瓜')      // 我叫 小愛,我 28 歲了, 我喜歡吃 冬瓜

未完,待更新


謝謝你閱讀到了最後
期待你,點贊、評論、交流

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章