深入理解JavaScript柯里化的概念和原理
引言
在 JS 編程中, 函數(shù)是一等公民, 具備了強(qiáng)大的靈活性和復(fù)用性。而 柯里化 作為一種高階技術(shù), 可以進(jìn)一步提升函數(shù)的復(fù)用性和靈活性。通過柯里化, 可以大大簡(jiǎn)化函數(shù)的調(diào)用方式, 并創(chuàng)建更加靈活和可復(fù)用的函數(shù)
在本篇博客中, 我們將深入探討 JS 中柯里化的概念和原理, 并了解其在實(shí)際開發(fā)中的應(yīng)用場(chǎng)景。通過學(xué)習(xí)柯里化, 您將能夠編寫出更加簡(jiǎn)潔、可讀性更高且易于維護(hù)的代碼, 提升自己的 JS 編程技能
讓我們開始探索 JS 柯里化的奇妙世界吧!
一、什么是柯里化
柯里化 (Currying), 又稱 部分求值 (Partial Evaluation), 是函數(shù)編程的一種高級(jí)技巧, 通常只需要傳遞給函數(shù) 一部分參數(shù) 來調(diào)用它, 讓它返回一個(gè)新的函數(shù)去 處理剩下的參數(shù)
如下 ???? 代碼, sum 是一個(gè)常規(guī)求和函數(shù), sumCurry 則是使用 柯里化 思想編寫的一個(gè)求和函數(shù), 它們區(qū)別就很明顯了, sum 在執(zhí)行時(shí)需要一次性進(jìn)行傳參, 但是 sumCurry 則不是, 它可以進(jìn)行進(jìn)行 分批傳入?yún)?shù)
// 常規(guī)求和函數(shù)
const sum = (a, b) => {
console.log(a + b)
}
sum(1, 2) // 3// 使用「柯里化」思想編寫的一個(gè)求和函數(shù)
const sumCurry = (a) => (b) => {
console.log(a + b)
}
sumCurry(1)(2) // 3補(bǔ)充說明:
柯里化是函數(shù)高級(jí)技巧, 允許分批次處理參數(shù), 很多文章更多討論的是如何將一個(gè)普通函數(shù)轉(zhuǎn)為柯里化函數(shù), 我們需要將這兩者區(qū)分開來,柯里化并不是指將一個(gè)函數(shù)從可調(diào)用的f(a, b, c)轉(zhuǎn)換為可調(diào)用的f(a)(b)(c)這個(gè)過程, 而是指轉(zhuǎn)換后的函數(shù)被稱為柯里化
二、柯里化作用
柯里化 的優(yōu)勢(shì)在于它為函數(shù)提供了更高的 靈活性 和 復(fù)用性, 我們可以先提供部分函數(shù)參數(shù), 并在后續(xù)調(diào)用中根據(jù)需要提供剩余的參數(shù), 這種靈活性使得代碼更具可讀性、可維護(hù)性, 并且能夠創(chuàng)建具有不同功能的相關(guān)函數(shù), 下面我來看幾個(gè)具體的例子, 感受下 柯里化 的魅力
2.1 參數(shù)復(fù)用
如下代碼, sumCurry 是 柯里化 函數(shù), 同時(shí)存在變量 age, 基于 age 我們調(diào)用了函數(shù) sumCurry 獲得到一個(gè)新的函數(shù) addAge, 在后面我們可以隨時(shí)調(diào)用該函數(shù), 基于最初給的的參數(shù) age 追加數(shù)值
// 使用「柯里化」思想編寫的一個(gè)求和函數(shù)
const sumCurry = (a) => (b) => {
console.log(a + b)
}
const age = 18
const addAge = sumCurry(age)
addAge(1) // 19
addAge(10) // 28
addAge(8) // 26
addAge(0) // 182.2 函數(shù)復(fù)用
下面我們有這么一個(gè)需求, 需要抽取對(duì)象數(shù)組中 name 屬性, 并返回一個(gè)數(shù)組, 可實(shí)現(xiàn)代碼如下:
const users = [
{ name: 'lh', age: 18 },
{ name: 'myj', age: 28 },
{ name: 'jl', age: 20 },
]
const names = users.map(v => v.name)
console.log(names) // ['lh', 'myj', 'jl']上面 ???? 代碼通過 map 來抽取數(shù)組對(duì)象中的 name 的屬性, 接下來我們嘗試使用 柯里化 對(duì)代碼進(jìn)行簡(jiǎn)單的優(yōu)化:
// 使用「柯里化」創(chuàng)建一個(gè)提供 map 使用的、可復(fù)用函數(shù)
const prop = (key) => (obj) => obj[key]
const users = [
{ name: 'lh', age: 18 },
{ name: 'myj', age: 28 },
{ name: 'jl', age: 20 },
]
const names = users.map(prop('name'))
console.log(names) // ['lh', 'myj', 'jl']上面代碼很簡(jiǎn)單, 但卻是 柯里化 的一個(gè)實(shí)踐, 有了 prop 函數(shù), 我們就可以很方便的, 抽取對(duì)象數(shù)組中指定元素, 而且在代碼閱讀上也相對(duì)來說清晰很多, 比如在上面基礎(chǔ)之上我們想要獲取所有 age 只需要這么編寫即可:
const ages = users.map(prop('age'))2.3 提前計(jì)算
如下代碼, 是常見的一種兼容性代碼的寫法, 代碼中通過判斷 addEventListener 以及 attachEvent 為現(xiàn)代瀏覽器或 IE 瀏覽器的事件添加方法
const addEvent = (el, type, fn, capture) => {
if (window.addEventListener) {
el.addEventListener(type, (e) => {
fn.call(el, e);
}, capture);
} else if (window.attachEvent) {
el.attachEvent("on" + type, (e) => {
fn.call(el, e);
});
}
};上面 ???? 代碼寫法沒啥毛病, 中規(guī)中距, 唯一的毛病可能就是在每次執(zhí)行 addEvent 時(shí)都需要進(jìn)行一次判斷, 這里我們就可以通過 柯里化 改造下, 提前 對(duì)兼容性進(jìn)行判斷, 最后返回添加事件的一個(gè) 主體方法
// 柯里化, 提前計(jì)算, 返回主體方法
const useEvent = () => {
if (window.addEventListener) {
return (el, sType, fn, capture) => {
el.addEventListener(sType, (e) => {
fn.call(el, e);
}, (capture));
};
} else if (window.attachEvent) {
return (el, sType, fn, capture) => {
el.attachEvent("on" + sType, (e) => {
fn.call(el, e);
});
};
}
}
const addEvent = useEvent()2.4 延遲計(jì)算(運(yùn)行)
延遲計(jì)算其實(shí)也好理解, 就是每次函數(shù)執(zhí)行都會(huì) 收集一部分參數(shù), 直到所有參數(shù)都準(zhǔn)備完畢, 才會(huì)進(jìn)行計(jì)算, 如下代碼:
// 使用「柯里化」思想編寫的一個(gè)求和函數(shù)
const sumCurry = (a) => (b) => (c) => (d) => {
console.log(a + b + c + d)
}
sumCurry(1)(2)(3)(4) // 10三、普通函數(shù)如果轉(zhuǎn)換為「柯里化」
接下來我們嘗試寫一個(gè)轉(zhuǎn)換函數(shù)
curry, 通過它我們可以將普通函數(shù)f(a, b, c, d)轉(zhuǎn)為柯里化函數(shù)f(a)(b)(c), 轉(zhuǎn)換后柯里化函數(shù)的精髓在于: 接收一部分參數(shù), 返回一個(gè)函數(shù)接收剩余參數(shù), 當(dāng)接收到足夠參數(shù)后, 執(zhí)行原函數(shù)
如下代碼所示, 我們實(shí)現(xiàn)了簡(jiǎn)單的一個(gè)轉(zhuǎn)換函數(shù) curry, _curry 是個(gè)中轉(zhuǎn)函數(shù), 也是重點(diǎn):
_curry本身就是個(gè)柯里化函數(shù), 它接收一個(gè)原函數(shù)、以及當(dāng)前已經(jīng)接收到的參數(shù)_curry返回一個(gè)新的函數(shù), 用于接收剩余的參數(shù)- 新函數(shù)接收若干參數(shù), 內(nèi)部會(huì)將之前收集的參數(shù)以及接收到的參數(shù)進(jìn)行合并, 并對(duì)
參數(shù)個(gè)數(shù)進(jìn)行判斷, 如果接收到足夠的參數(shù)了, 則執(zhí)行原函數(shù), 如果接收的參數(shù)不夠則執(zhí)行_curry函數(shù), 并返回一個(gè)新的函數(shù)繼續(xù)接收處理剩余的參數(shù)
/**
* 中轉(zhuǎn)函數(shù)
* @param fun 待柯里化的原函數(shù)
* @param allArgs 已接收的參數(shù)列表
* @returns {Function} 返回一個(gè)接收剩余參數(shù)的函數(shù)
*/
const _curry = (fun, ...allArgs) => {
// 1. 返回一個(gè)接收剩余參數(shù)的函數(shù)
return (...currentArgs) => {
// 2. 當(dāng)前接收到的所有參數(shù)
const _args = [...allArgs, ...currentArgs]
// 3. 接收到的參數(shù)大于或等于函數(shù)本身的參數(shù)時(shí), 執(zhí)行原函數(shù)
if (_args.length >= fun.length) {
return fun.call(this, ..._args)
}
// 4. 繼續(xù)執(zhí)行 _curry 返回一個(gè)接收剩余參數(shù)的函數(shù)
return _curry.call(this, fun, ..._args)
}
}
/**
* 將函數(shù)柯里化
* @param fun 待柯里化的原函數(shù)
* @returns {Function} 返回「柯里化」函數(shù)
*/
const curry = (fun) => _curry.call(this, fun)
// 測(cè)試
const sum = (a, b, c) => (a + b + c) // 原函數(shù)
const currySum = curry(sum) // 柯里化 函數(shù)
currySum(1)(2)(3) // 6
currySum(1)(2, 3) // 6
currySum(1, 2, 3) // 6四、參數(shù)個(gè)數(shù)不定
下面我們回到 柯里化 本身, 還記得我們上文寫的 sumCurry 函數(shù)嗎? 該 柯里化 函數(shù)每次調(diào)用都只能傳一個(gè)參數(shù)
// 使用「柯里化」思想編寫的一個(gè)求和函數(shù)
const sumCurry = (a) => (b) => {
return a + b
}
sumCurry(1)(2) // 3這里我們對(duì) sumCurry 進(jìn)行改造, 允許在每次調(diào)用時(shí)傳遞任意個(gè)數(shù)參數(shù)
// 使用「柯里化」思想編寫的一個(gè)求和函數(shù)
const sumCurry = (...preArgs) => (...nextArgs) => {
return [...preArgs, ...nextArgs].reduce((total, ele) => (total + ele), 0)
}
sumCurry(1, 2, 3)() // 6
sumCurry(1, 2, 3)(4) // 10
sumCurry()(1, 4) // 5
sumCurry(1)(2) // 3五、無限參數(shù)
上文我們所演示的 柯里化 函數(shù)允許被 連續(xù)調(diào)用次數(shù) 是固定的, 如果我們要實(shí)現(xiàn)一個(gè)可以 無限調(diào)用 的柯里化函數(shù)又該怎么處理呢? 實(shí)現(xiàn)原理其實(shí)也簡(jiǎn)單, 重點(diǎn)就以下兩點(diǎn):
- 只需要寫個(gè)
循環(huán)調(diào)用即可, 函數(shù)每次返回新函數(shù), 新函數(shù)調(diào)用原函數(shù)并將上一次結(jié)果傳給原函數(shù) - 修改函數(shù)
toString方法, 用于取值
// 使用「柯里化」思想編寫的一個(gè)求和函數(shù)
const sumCurry = (...preArgs) => {
// 1. 計(jì)算當(dāng)前結(jié)果
const preTotal = preArgs.reduce((total, ele) => (total + ele), 0)
// 2. 使用 bind, 基于原函數(shù)創(chuàng)建新函數(shù), 并將上一次結(jié)果作為第一個(gè)參數(shù)
const _sumCurry = sumCurry.bind(this, preTotal)
// 3. 修改 toString 返回值, 用于值值
_sumCurry.toString = () => preTotal
return _sumCurry
}
// 使用 + 強(qiáng)制調(diào)用 toString 方法
console.log(+sumCurry(1)) // 1
console.log(+sumCurry(1, 2, 3)) // 6
console.log(+sumCurry(1)(2)) // 3
console.log(+sumCurry(1)(2)(3)(4)(5)) // 15
console.log(+sumCurry(1)()(3)(4)(5)) // 13六、柯里化和閉包的關(guān)系
閉包 和 柯里化 是兩個(gè)不同的概念, 但它們之間有一定的關(guān)聯(lián):
在編程中, 閉包允許函數(shù)捕獲并訪問其定義時(shí)的上下文中的變量, 即使在其定義環(huán)境之外被調(diào)用時(shí)也可以使用這些變量, 閉包可以通過函數(shù)返回函數(shù)的方式創(chuàng)建, 從而使得內(nèi)部函數(shù)可以訪問外部函數(shù)的變量
柯里化是指高階函數(shù)使用技巧, 柯里化函數(shù)的特點(diǎn)是, 允許被連續(xù)調(diào)用
f(a)(b)(c)每次調(diào)用傳遞若干參數(shù), 同時(shí)返回一個(gè)接受剩余參數(shù)的新函數(shù), 直到所有參數(shù)都被傳遞完畢, 才會(huì)執(zhí)行主體邏輯閉包和柯里化之間的關(guān)系在于,
柯里化函數(shù)通常會(huì)使用閉包來實(shí)現(xiàn), 當(dāng)我們將一個(gè)函數(shù)進(jìn)行柯里化時(shí), 每次返回一個(gè)新的函數(shù), 這個(gè)新的函數(shù)會(huì)捕獲前一次調(diào)用時(shí)的參數(shù)和上下文, 這個(gè)上下文就形成了閉包, 使得新函數(shù)可以在后續(xù)調(diào)用中繼續(xù)訪問之前傳遞的參數(shù)和上下文
// 形成閉包, 返回的新函數(shù), 允許訪問父級(jí)函數(shù)的變量 a
const sumCurry = (a) => (b) => {
console.log(a + b)
}到此這篇關(guān)于深入理解JavaScript柯里化的概念和原理的文章就介紹到這了,更多相關(guān)JavaScript 柯里化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IE 下Enter提交表單存在重復(fù)提交問題的解決方法
這篇文章主要介紹了IE 下Enter提交表單存在重復(fù)提交問題的解決方法,需要的朋友可以參考下2014-05-05
javascript eval和JSON之間的聯(lián)系
本文著重解釋eval函數(shù)和JSON數(shù)據(jù)格式之間的聯(lián)系以及一些細(xì)節(jié)上的問題。2009-12-12
JavaScript實(shí)現(xiàn)獲取設(shè)備網(wǎng)絡(luò)連接信息
作為前端開發(fā),做好用戶體驗(yàn)是很重要的,日常開發(fā)中我們經(jīng)??梢杂龅接脩艟W(wǎng)速慢導(dǎo)致靜態(tài)資源加載慢,從而給影響用戶體驗(yàn),所以本文來和大家分享一個(gè)有趣的API,可以實(shí)現(xiàn)獲取網(wǎng)絡(luò)信息2023-05-05
textarea支持圖形編輯的實(shí)現(xiàn)方法
本來以為只有iframe支持編輯了,今天突然發(fā)現(xiàn)textarea也支持編輯 :( 是不是我太愚鈍了? textarea不能用innerHTML來插入html,而用appendChild 這樣的話稍微修改下,評(píng)論等簡(jiǎn)單的textarea的表情插入就可以直接顯示出來了 而不是只顯示emot了 比較實(shí)用標(biāo)題起的確切,但如果叫“現(xiàn)textarea支持編輯”似乎更廢話2008-03-03
了解Javascript中函數(shù)作為對(duì)象的魅力
這篇文章主要介紹了了解Javascript中函數(shù)作為對(duì)象的魅力,javascript中函數(shù)可以有屬性,可以有方法, 可以享有所有對(duì)象所擁有的特性。并且最重要的,她還可以直接被調(diào)用,需要的朋友可以參考下2019-06-06
深入解析Javascript閉包的功能及實(shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)解析Javascript閉包的功能及實(shí)現(xiàn)方法,感興趣的小伙伴們可以參考一下2016-07-07
微信小程序頁(yè)面滑動(dòng)屏幕加載數(shù)據(jù)效果
這篇文章主要為大家詳細(xì)介紹了微信小程序頁(yè)面滑動(dòng)屏幕加載數(shù)據(jù)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08

