JavaScript中的柯里化(Currying)和反柯里化(Uncurrying)是兩種很有用的技術(shù),可以幫助我們寫出更加優(yōu)雅、泛用的函數(shù)。本文將首先介紹柯里化和反柯里化的概念、實(shí)現(xiàn)原理和應(yīng)用場(chǎng)景,通過大量的代碼示例幫助讀者深入理解這兩種技術(shù)的用途。
柯里化(Currying)是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)。這個(gè)技術(shù)由數(shù)學(xué)家Haskell Curry命名。
簡(jiǎn)單來說,柯里化可以將使用多個(gè)參數(shù)的函數(shù)轉(zhuǎn)換成一系列使用一個(gè)參數(shù)的函數(shù)。例如:
function add(a, b) { return a + b; }// 柯里化后function curriedAdd(a) { return function(b) { return a + b; }}
實(shí)現(xiàn)柯里化的關(guān)鍵是通過閉包保存函數(shù)參數(shù)。以下是柯里化函數(shù)的一般模式:
function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } else { return function(...args2) { return curried.apply(this, args.concat(args2)); } } }}
curry函數(shù)接受一個(gè)fn函數(shù)為參數(shù),返回一個(gè)curried函數(shù)。curried函數(shù)檢查接收的參數(shù)個(gè)數(shù)args.length是否滿足fn函數(shù)需要的參數(shù)個(gè)數(shù)fn.length。如果滿足,則直接調(diào)用fn函數(shù);如果不滿足,則繼續(xù)返回curried函數(shù)等待接收剩余參數(shù)。
這樣通過閉包保存每次收到的參數(shù),直到參數(shù)的總數(shù)達(dá)到fn需要的參數(shù)個(gè)數(shù),然后將保存的參數(shù)全部 apply 給 fn執(zhí)行。
利用這個(gè)模式可以輕松將普通函數(shù)柯里化:
// 普通函數(shù)function add(a, b) { return a + b;} // 柯里化后let curriedAdd = curry(add); curriedAdd(1)(2); // 3
柯里化可以讓我們輕松復(fù)用參數(shù)。例如:
function discounts(price, discount) { return price * discount;}// 柯里化后const tenPercentDiscount = discounts(0.1); tenPercentDiscount(500); // 50tenPercentDiscount(200); // 20
有時(shí)我們需要提前返回函數(shù)的副本給其他模塊使用,這時(shí)可以用柯里化。
// 模塊Afunction ajax(type, url, data) { // 發(fā)送ajax請(qǐng)求}// 柯里化后export const getJSON = curry(ajax)('GET');// 模塊Bimport { getJSON } from './moduleA'; getJSON('/users', {name: 'John'});
柯里化函數(shù)在調(diào)用時(shí)并不會(huì)立即執(zhí)行,而是返回一個(gè)函數(shù)等待完整的參數(shù)后再執(zhí)行。這讓我們可以更加靈活地控制函數(shù)的執(zhí)行時(shí)機(jī)。
let log = curry(console.log);log('Hello'); // 不會(huì)立即執(zhí)行setTimeout(() => { log('Hello'); // 2秒后執(zhí)行}, 2000);
反柯里化(Uncurrying)與柯里化相反,它將一個(gè)接受單一參數(shù)的函數(shù)轉(zhuǎn)換成接受多個(gè)參數(shù)的函數(shù)。
// 柯里化函數(shù) function curriedAdd(a) { return function(b) { return a + b; }}// 反柯里化后function uncurriedAdd(a, b) { return a + b; }
反柯里化的關(guān)鍵是通過遞歸不停調(diào)用函數(shù)并傳入?yún)?shù),Until參數(shù)的數(shù)量達(dá)到函數(shù)需要的參數(shù)個(gè)數(shù)。
function uncurry(fn) { return function(...args) { let context = this; return args.reduce((acc, cur) => { return acc.call(context, cur); }, fn); }}
uncurry 接收一個(gè)函數(shù) fn,返回一個(gè)函數(shù)。這個(gè)函數(shù)利用reduce不停調(diào)用 fn 并傳入?yún)?shù),Until 把a(bǔ)rgs所有參數(shù)都傳給 fn。
利用這個(gè)模式可以輕松實(shí)現(xiàn)反柯里化:
const curriedAdd = a => b => a + b;const uncurriedAdd = uncurry(curriedAdd);uncurriedAdd(1, 2); // 3
有時(shí)我們會(huì)從其他模塊接收到一個(gè)柯里化的函數(shù),但我們的接口需要一個(gè)普通的多參數(shù)函數(shù)。這時(shí)可以通過反柯里化來實(shí)現(xiàn)統(tǒng)一。
// 模塊A導(dǎo)出export const curriedGetUser = id => callback => { // 調(diào)用callback(user)};// 模塊B中import { curriedGetUser } from './moduleA';// 反柯里化以符合接口const getUser = uncurry(curriedGetUser); getUser(123, user => { // use user});
反柯里化可以讓我們以任意順序 passes 入?yún)?shù),增加了函數(shù)的靈活性。
const uncurriedLog = uncurry(console.log);uncurriedLog('a', 'b'); uncurriedLog('b', 'a'); // 參數(shù)順序靈活
柯里化函數(shù)不容易實(shí)現(xiàn)默認(rèn)參數(shù),而反柯里化后可以方便地設(shè)置默認(rèn)參數(shù)。
function uncurriedRequest(url, method='GET', payload) { // 請(qǐng)求邏輯}
這是一道典型的柯里化面試題。解析:
function curry(fn) { return function curried(a) { return function(b) { return fn(a, b); } }}function add(a, b) { return a + b;}const curriedAdd = curry(add);curriedAdd(1)(2)(3); // 6
利用柯里化技術(shù),我們可以將普通的 add 函數(shù)轉(zhuǎn)化為 curriedAdd,它每次只接收一個(gè)參數(shù),并返回函數(shù)等待下一個(gè)參數(shù),從而實(shí)現(xiàn)了 add(1)(2)(3) 的效果。
compose函數(shù)可以將多個(gè)函數(shù)合并成一個(gè)函數(shù),這也是一道常見的柯里化面試題。解析:
function compose(fn1) { return function(fn2) { return function(x) { return fn1(fn2(x)); }; };}function double(x) { return x * 2;}function square(x) { return x * x;}const func = compose(double)(square);func(5); // 50
利用柯里化,我們創(chuàng)建了一個(gè)單參數(shù)的 compose 函數(shù),它每次返回一個(gè)函數(shù)等待下一個(gè)函數(shù)參數(shù)。這樣最終實(shí)現(xiàn)了 compose(double)(square) 的效果。
Function.bind 函數(shù)實(shí)現(xiàn)了部分參數(shù)綁定,這本質(zhì)上是一個(gè)反柯里化的過程。解析:
Function.prototype.uncurriedBind = function(context) { const fn = this; return function(...args) { return fn.call(context, ...args); } }function greet(greeting, name) { console.log(greeting, name);}const greetHello = greet.uncurriedBind('Hello');greetHello('John'); // Hello John
uncurriedBind 通過遞歸調(diào)用并傳參實(shí)現(xiàn)了反柯里化,使 bind 參數(shù)從兩步變成一步傳入,這也是 Function.bind 的工作原理。
柯里化和反柯里化都是非常有用的編程技巧,讓我們可以寫出更加靈活通用的函數(shù)。理解這兩種技術(shù)的實(shí)現(xiàn)原理可以幫助我們更好地運(yùn)用它們。在編碼中,我們可以根據(jù)需要決定是將普通函數(shù)柯里化,還是將柯里化函數(shù)反柯里化。合理運(yùn)用這兩種技術(shù)可以大大提高我們的編程效率。
本文鏈接:http://www.tebozhan.com/showinfo-26-125-0.html三言兩語(yǔ)說透柯里化和反柯里化
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com