最近跟着黃軼老師
學習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 歲了, 我喜歡吃 冬瓜
未完,待更新
謝謝你閱讀到了最後
期待你,點贊、評論、交流