成都網(wǎng)站制作公司湘潭網(wǎng)站設(shè)計
文章目錄
- 基本概念
- 柯里化(Currying)是什么?
- 通用的柯里化實(shí)現(xiàn)
- ES5 實(shí)現(xiàn)
- ES6 實(shí)現(xiàn)
基本概念
在講柯里化之前我們先來了解一些基本概念:
Function.length: length 屬性指明函數(shù)的形參個數(shù)
function func1() {}
function func2(a, b) {}console.log(func1.length); // 0
console.log(func2.length); // 2
Arguments 對象: 是一個對應(yīng)于傳遞給函數(shù)的參數(shù)的類數(shù)組對象
- arguments 對象是所有(非箭頭)函數(shù)中都可用的局部變量
- “類數(shù)組”意味著 arguments 有長度屬性,并且屬性的索引是從零開始的,但是它沒有 Array的內(nèi)置方法,例如 forEach() 和 map()都是沒有的
- arguments.length:本次函數(shù)調(diào)用時傳入函數(shù)的實(shí)參數(shù)量(這個數(shù)字可以比形參數(shù)量大,也可以比形參數(shù)量小)
- arguments 可以被轉(zhuǎn)換為一個真正的 Array
// arguments 轉(zhuǎn)換為一個真正的 Array
var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);// ES6
const args = Array.from(arguments);
const args = [...arguments];
這里小提一下: How does Array.prototype.slice.call
work?
.call()和.apply()方法允許您在函數(shù)中手動設(shè)置this的值。因此,如果我們將.slice()中的this的值設(shè)置為一個類似數(shù)組的對象,.slice()將假設(shè)它正在處理一個數(shù)組,并將執(zhí)行它的任務(wù)。
剩余參數(shù):允許我們將一個不定數(shù)量的參數(shù)表示為一個數(shù)組
如果函數(shù)的最后一個命名參數(shù)以…為前綴,則它將成為一個由剩余參數(shù)組成的真數(shù)組,其中從0(包括)到theArgs.length(排除)的元素由傳遞給函數(shù)的實(shí)際參數(shù)提供
function sum1(...theArgs) {console.log(theArgs)
}sum1(1, 2, 3); // [1, 2, 3]function sum2(a, ...theArgs) {console.log(theArgs)
}sum2(1, 2, 3); // [2, 3]
注:剩余參數(shù)只包含那些沒有對應(yīng)形參的實(shí)參,而 arguments 對象包含了傳給函數(shù)的所有實(shí)參。
柯里化(Currying)是什么?
柯里化,是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。核心思想是把多參數(shù)傳入的函數(shù)拆成單參數(shù)(或部分)函數(shù),內(nèi)部再返回調(diào)用下一個單參數(shù)(或部分)函數(shù),依次處理剩余的參數(shù)。
舉個例子:
// 傳統(tǒng)寫法
function sum(a, b, c) {return a + b + c
}
console.log(sum(1,2,3)); // 6// 柯里化
function sum(a) {return function (b) {return function (c) {return a + b + c}}
}
console.log(sum(1)(2)(3)); // 6// 將 a + b + c 的操作提取成一個方法
function curry(fn) {return function (a) {return function (b) {return function (c) {return fn(a, b, c);};};}
}function add(a, b, c) {return a + b + c;
}
var sum = curry(add);
console.log(sum(1)(2)(3)); // 6
這樣就對一個函數(shù)實(shí)現(xiàn)了柯里化。但這個 curry 函數(shù)并不通用,實(shí)際開發(fā)過程中參數(shù)數(shù)量具有不確定性:比如 sum(1)(2,3) 或 sum(1,2)(3) ,或是一個函數(shù)需要傳 n 個參數(shù),如下:
function curry(fn) {return function (a1) {return function (a2) {return function (a3) {//......return function (aN) {return fn(a1, a2, a3, ...aN);};};};};
}
我們需要實(shí)現(xiàn)一個通用的柯里化函數(shù),對于參數(shù)的不確定性,我們可以通過使用 arguments 和 遞歸 來處理。
通用的柯里化實(shí)現(xiàn)
在下面實(shí)現(xiàn)代碼中所用到的一些屬性、對象、方法我們已在基本概念中介紹過,方便大家理解。
ES5 實(shí)現(xiàn)
// 法一 (遞歸調(diào)用 judge)
function curry(fn) {return function judge() {// arguments 轉(zhuǎn)換為數(shù)組var _args = Array.prototype.slice.call(arguments);// 判斷首次(或組合后)所傳的參數(shù)數(shù)量 是否小于 fn 方法的形參個數(shù)if (_args.length < fn.length) {// 首次(或組合后)所傳的參數(shù)數(shù)量 小于 fn 方法的形參個數(shù), 內(nèi)部再返回調(diào)用下一個單參數(shù)(或部分)函數(shù)return function (){// 獲取當(dāng)前參數(shù)數(shù)組var _args2 = Array.prototype.slice.call(arguments);// 將之前所傳的參數(shù)和當(dāng)前的參數(shù)組合,遞歸調(diào)用return judge.apply(this, _args.concat(_args2))};} else {// 首次(或組合后)所傳的參數(shù)數(shù)量 等于 fn 方法的形參個數(shù),則直接調(diào)用 fn 函數(shù)return fn.apply(this, _args);}}
}// 法二 (遞歸調(diào)用 curry)
function curry(fn, args) {var args = args || []; // 首次為 []return function () {var _args = args.concat([].slice.call(arguments));if (_args.length < fn.length) {return curry.call(this, fn, _args);} else {return fn.apply(this, _args);}}
}function add(a, b ,c) {return a + b + c;
}const addCurry = curry(add);console.log(addCurry(1,2)(3)) // 6console.log(addCurry(1)(2)(3)) // 6console.log(addCurry(1)(2,3)) // 6
簡而言之,就是將所有實(shí)際的傳參組合起來,如果實(shí)際參數(shù)個數(shù)等于 fn 方法的形參個數(shù)后便調(diào)用 fn 方法。
ES6 實(shí)現(xiàn)
// 法一 (遞歸調(diào)用 judge)
const curry = (fn) => {return function judge(...args) {if (args.length < fn.length) {return function (...args2) {return judge(...args, ...args2);};}return fn(...args);};
}// 法二 (遞歸調(diào)用 curry)
const curry = (fn, ...arg) => (arg.length >= fn.length ? fn(...arg) : (..._arg) => curry(fn, ...arg, ..._arg))function add(a, b ,c) {return a + b + c;
}const addCurry = curry(add);console.log(addCurry(1,2)(3)) // 6console.log(addCurry(1)(2)(3)) // 6console.log(addCurry(1)(2,3)) // 6
想要明白柯里化函數(shù),還是建議大家動手寫寫,在寫的過程中分析執(zhí)行步驟,方便理解。