摘要:柯里化通用式上面的柯里化函數(shù)沒涉及到高階函數(shù),也不具備通用性,無法轉(zhuǎn)換形參個數(shù)任意或未知的函數(shù),我們接下來封裝一個通用的柯里化轉(zhuǎn)換函數(shù),可以將任意函數(shù)轉(zhuǎn)換成柯里化。
在 JavaScript 中,柯里化和反柯里化是高階函數(shù)的一種應(yīng)用,在這之前我們應(yīng)該清楚什么是高階函數(shù),通俗的說,函數(shù)可以作為參數(shù)傳遞到函數(shù)中,這個作為參數(shù)的函數(shù)叫回調(diào)函數(shù),而擁有這個參數(shù)的函數(shù)就是高階函數(shù),回調(diào)函數(shù)在高階函數(shù)中調(diào)用并傳遞相應(yīng)的參數(shù),在高階函數(shù)執(zhí)行時,由于回調(diào)函數(shù)的內(nèi)部邏輯不同,高階函數(shù)的執(zhí)行結(jié)果也不同,非常靈活,也被叫做函數(shù)式編程。
在 JavaScript 中,函數(shù)柯里化是函數(shù)式編程的重要思想,也是高階函數(shù)中一個重要的應(yīng)用,其含義是給函數(shù)分步傳遞參數(shù),每次傳遞部分參數(shù),并返回一個更具體的函數(shù)接收剩下的參數(shù),這中間可嵌套多層這樣的接收部分參數(shù)的函數(shù),直至返回最后結(jié)果。
1、最基本的柯里化拆分// 柯里化拆分 // 原函數(shù) function add(a, b, c) { return a + b + c; } // 柯里化函數(shù) function addCurrying(a) { return function (b) { return function (c) { return a + b + c; } } } // 調(diào)用原函數(shù) add(1, 2, 3); // 6 // 調(diào)用柯里化函數(shù) addCurrying(1)(2)(3) // 6
被柯里化的函數(shù) addCurrying 每次的返回值都為一個函數(shù),并使用下一個參數(shù)作為形參,直到三個參數(shù)都被傳入后,返回的最后一個函數(shù)內(nèi)部執(zhí)行求和操作,其實是充分的利用了閉包的特性來實現(xiàn)的。
2、柯里化通用式上面的柯里化函數(shù)沒涉及到高階函數(shù),也不具備通用性,無法轉(zhuǎn)換形參個數(shù)任意或未知的函數(shù),我們接下來封裝一個通用的柯里化轉(zhuǎn)換函數(shù),可以將任意函數(shù)轉(zhuǎn)換成柯里化。
// 柯里化通用式 ES5 function currying(func, args) { // 形參個數(shù) var arity = func.length; // 上一次傳入的參數(shù) var args = args || []; return function () { // 將參數(shù)轉(zhuǎn)化為數(shù)組 var _args = [].slice.call(arguments); // 將上次的參數(shù)與當(dāng)前參數(shù)進(jìn)行組合并修正傳參順序 Array.prototype.unshift.apply(_args, args); // 如果參數(shù)不夠,返回閉包函數(shù)繼續(xù)收集參數(shù) if(_args.length < arity) { return currying.call(null, func, _args); } // 參數(shù)夠了則直接執(zhí)行被轉(zhuǎn)化的函數(shù) return func.apply(null, _args); } }
上面主要使用的是 ES5 的語法來實現(xiàn),大量的使用了 call 和 apply,下面我們通過 ES6 的方式實現(xiàn)功能完全相同的柯里化轉(zhuǎn)換通用式。
// 柯里化通用式 ES6 function currying(func, args = []) { let arity = func.length; return function (..._args) { _args.unshift(...args); if(_args.length < arity) { return currying(func, _args); } return func(..._args); } }
函數(shù) currying 算是比較高級的轉(zhuǎn)換柯里化的通用式,可以隨意拆分參數(shù),假設(shè)一個被轉(zhuǎn)換的函數(shù)有多個形參,我們可以在任意環(huán)節(jié)傳入任意個數(shù)的參數(shù)進(jìn)行拆分,舉一個例子,假如 5 個參數(shù),第一次可以傳入 2 個,第二次可以傳入 1 個, 第三次可以傳入剩下的,也有其他的多種傳參和拆分方案,因為在 currying 內(nèi)部收集參數(shù)的同時按照被轉(zhuǎn)換函數(shù)的形參順序進(jìn)行了更正。
柯里化的一個很大的好處是可以幫助我們基于一個被轉(zhuǎn)換函數(shù),通過對參數(shù)的拆分實現(xiàn)不同功能的函數(shù),如下面的例子。
// 柯里化通用式應(yīng)用 —— 普通函數(shù) // 被轉(zhuǎn)換函數(shù),用于檢測傳入的字符串是否符合正則表達(dá)式 function checkFun(reg, str) { return reg.test(str); } // 轉(zhuǎn)換柯里化 const check = currying(checkFun); // 產(chǎn)生新的功能函數(shù) const checkPhone = check(/^1[34578]d{9}$/); const checkEmail = check(/^(w)+(.w+)*@(w)+((.w+)+)$/);
上面的例子根據(jù)一個被轉(zhuǎn)換的函數(shù)通過轉(zhuǎn)換變成柯里化函數(shù),并用 check 變量接收,以后每次調(diào)用 check 傳遞不同的正則就會產(chǎn)生一個檢測不同類型字符串的功能函數(shù)。
這種使用方式同樣適用于被轉(zhuǎn)換函數(shù)是高階函數(shù)的情況,比如下面的例子。
// 柯里化通用式應(yīng)用 —— 高階函數(shù) // 被轉(zhuǎn)換函數(shù),按照傳入的回調(diào)函數(shù)對傳入的數(shù)組進(jìn)行映射 function mapFun(func, array) { return array.map(func); } // 轉(zhuǎn)換柯里化 const getNewArray = currying(mapFun); // 產(chǎn)生新的功能函數(shù) const createPercentArr = getNewArray(item => `${item * 100}%`); const createDoubleArr = getNewArray(item => item * 2); // 使用新的功能函數(shù) let arr = [1, 2, 3, 4, 5]; let percentArr = createPercentArr(arr); // ["100%", "200%", "300%", "400%", "500%",] let doubleArr = createDoubleArr(arr); // [2, 4, 6, 8, 10]3、柯里化與 bind
bind 方法是經(jīng)常使用的一個方法,它的作用是幫我們將調(diào)用 bind 函數(shù)內(nèi)部的上下文對象 this 替換成我們傳遞的第一個參數(shù),并將后面其他的參數(shù)作為調(diào)用 bind 函數(shù)的參數(shù)。
// bind 方法原理模擬 // bind 方法的模擬 Function.prototype.bind = function (context) { var self = this; var args = [].slice.call(arguments, 1); return function () { return self.apply(context, args); } }
通過上面代碼可以看出,其實 bind 方法就是一個柯里化轉(zhuǎn)換函數(shù),將調(diào)用 bind 方法的函數(shù)進(jìn)行轉(zhuǎn)換,即通過閉包返回一個柯里化函數(shù),執(zhí)行該柯里化函數(shù)的時候,借用 apply 將調(diào)用 bind 的函數(shù)的執(zhí)行上下文轉(zhuǎn)換成了 context 并執(zhí)行,只是這個轉(zhuǎn)換函數(shù)沒有那么復(fù)雜,沒有進(jìn)行參數(shù)拆分,而是函數(shù)在調(diào)用的時候傳入了所有的參數(shù)。
反柯里化的思想與柯里化正好相反,如果說柯里化的過程是將函數(shù)拆分成功能更具體化的函數(shù),那反柯里化的作用則在于擴大函數(shù)的適用性,使本來作為特定對象所擁有的功能函數(shù)可以被任意對象所使用。
1、反柯里化通用式反柯里化通用式的參數(shù)為一個希望可以被其他對象調(diào)用的方法或函數(shù),通過調(diào)用通用式返回一個函數(shù),這個函數(shù)的第一個參數(shù)為要執(zhí)行方法的對象,后面的參數(shù)為執(zhí)行這個方法時需要傳遞的參數(shù)。
// 反柯里化通用式 ES5 function uncurring(fn) { return function () { // 取出要執(zhí)行 fn 方法的對象,同時從 arguments 中刪除 var obj = [].shift.call(arguments); return fn.apply(obj, arguments); } }
// 反柯里化通用式 ES6 function uncurring(fn) { return function (...args) { return fn.call(...args); } }
下面我們通過一個例子來感受一下反柯里化的應(yīng)用。
// 反柯里化通用式應(yīng)用 // 構(gòu)造函數(shù) F function F() {} // 拼接屬性值的方法 F.prototype.concatProps = function () { let args = Array.from(arguments); return args.reduce((prev, next) => `${this[prev]}&${this[next]}`); } // 使用 concatProps 的對象 let obj = { name: "Panda", age: 16 }; // 使用反柯里化進(jìn)行轉(zhuǎn)化 const concatProps = uncurring(F.prototype.concatProps); concatProps(obj, "name", "age"); // Panda&16
反柯里化還有另外一個應(yīng)用,用來代替直接使用 call 和 apply,比如檢測數(shù)據(jù)類型的 Object.prototype.toString 等方法,以往我們使用時是在這個方法后面直接調(diào)用 call 更改上下文并傳參,如果項目中多處需要對不同的數(shù)據(jù)類型進(jìn)行驗證是很麻的,常規(guī)的解決方案是封裝成一個檢測數(shù)據(jù)類型的模塊。
// 檢測數(shù)據(jù)類型常規(guī)方案 function checkType(val) { return Object.prototype.toString.call(val); }
如果需要這樣封裝的功能很多就麻煩了,代碼量也會隨之增大,其實我們也可以使用另一種解決方案,就是利用反柯里化通用式將這個函數(shù)轉(zhuǎn)換并將返回的函數(shù)用變量接收,這樣我們只需要封裝一個 uncurring 通用式就可以了。
// 反柯里化創(chuàng)建檢測類型函數(shù) const checkType = uncurring(Object.prototype.toString); checkType(1); // [object Number] checkType("hello"); // [object String] checkType(true); // [object Boolean]2、通過函數(shù)調(diào)用生成反柯里化函數(shù)
在 JavaScript 我們經(jīng)常使用面向?qū)ο蟮木幊谭绞?,在兩個類或構(gòu)造函數(shù)之間建立聯(lián)系實現(xiàn)繼承,如果我們對繼承的需求僅僅是希望一個構(gòu)造函數(shù)的實例能夠使用另一個構(gòu)造函數(shù)原型上的方法,那進(jìn)行繁瑣的繼承很浪費,簡單的繼承父子類的關(guān)系又不那么的優(yōu)雅,還不如之間不存在聯(lián)系。
// 將反柯里化方法擴展到函數(shù)原型 Function.prototype.uncurring = function () { var self = this; return function () { return Function.prototype.call.apply(self, arguments); } }
之前的問題通過上面給函數(shù)擴展的 uncurring 方法完全得到了解決,比如下面的例子。
// 函數(shù)應(yīng)用反柯里化原型方法 // 構(gòu)造函數(shù) function F() {} F.prototype.sayHi = function () { return "I"m " + this.name + ", " + this.age + " years old."; } // 希望 sayHi 方法被任何對象使用 sayHi = F.prototype.sayHi.uncurring(); sayHi({ name: "Panda", age: 20}); // I"m Panda, 20 years old.
在 Function 的原型對象上擴展的 uncurring 中,難點是理解 Function.prototype.call.apply,我們知道在 call 的源碼邏輯中 this 指的是調(diào)用它的函數(shù),在 call 內(nèi)部用第一個參數(shù)替換了這個函數(shù)中的 this,其余作為形參執(zhí)行了函數(shù)。
而在 Function.prototype.call.apply 中 apply 的第一個參數(shù)更換了 call 中的 this,這個用于更換 this 的就是例子中調(diào)用 uncurring 的方法 F.prototype.sayHi,所以等同于 F.prototype.sayHi.call,arguments 內(nèi)的參數(shù)會傳入 call 中,而 arguments 的第一項正是用于修改 F.prototype.sayHi 中 this 的對象。
看到這里你應(yīng)該對柯里化和反柯里化有了一個初步的認(rèn)識了,但要熟練的運用在開發(fā)中,還需要我們更深入的去了解它們內(nèi)在的含義。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/98307.html
摘要:與反柯里化什么是柯里化與反柯里化純函數(shù)函數(shù)結(jié)果只受傳入?yún)?shù)影響,參數(shù)一定,結(jié)果一定。寫法柯里化接受一次性傳入多個參數(shù)調(diào)用的函數(shù),也可以傳入部分參數(shù)調(diào)用,最后使它返回一個單一參數(shù)的函數(shù)去處理,并且返回。 call與apply反柯里化? 什么是柯里化與反柯里化?純函數(shù)函數(shù)結(jié)果只受傳入?yún)?shù)影響,參數(shù)一定,結(jié)果一定。高階函數(shù)一個函數(shù)可以接收另一個函數(shù)作為參數(shù),這種函數(shù)稱為高階函數(shù)。 funct...
摘要:函數(shù)的柯里化的基本使用方法和函數(shù)綁定是一樣的使用一個閉包返回一個函數(shù)。先來一段我自己實現(xiàn)的函數(shù)高程里面這么評價它們兩個的方法也實現(xiàn)了函數(shù)的柯里化。使用還是要根據(jù)是否需要對象響應(yīng)來決定。 奇怪,怎么把函數(shù)的柯里化和Redux中間件這兩個八竿子打不著的東西聯(lián)系到了一起,如果你和我有同樣疑問的話,說明你對Redux中間件的原理根本就不了解,我們先來講下什么是函數(shù)的柯里化?再來講下Redux的...
摘要:一直以來沒有對函數(shù)式編程有一個全面的學(xué)習(xí)和使用,或者說沒有一個深刻的思考。是不是輕松了其實函數(shù)式編程主張的就是以抽象的方式創(chuàng)建函數(shù)。后面咱們在系統(tǒng)性的學(xué)習(xí)下函數(shù)式編程。 一直以來沒有對函數(shù)式編程有一個全面的學(xué)習(xí)和使用,或者說沒有一個深刻的思考。最近看到一些博客文章,突然覺得函數(shù)式編程還是蠻有意思的。看了些書和文章。這里記載下感悟和收獲。 歡迎團隊姜某人多多指點@姜少。 由于博客秉持著簡...
摘要:原文鏈接和都支持函數(shù)的柯里化函數(shù)的柯里化還與的函數(shù)編程有很大的聯(lián)系如果你感興趣的話可以在這些方面多下功夫了解相信收獲一定很多看本篇文章需要知道的一些知識點函數(shù)部分的閉包高階函數(shù)不完全函數(shù)文章后面有對這些知識的簡單解釋大家可以看看什么是柯里化 原文鏈接 Haskell和scala都支持函數(shù)的柯里化,JavaScript函數(shù)的柯里化還與JavaScript的函數(shù)編程有很大的聯(lián)系,如果你感興...
摘要:今天了解到一個新名詞柯里化,研究一番后總結(jié)如下一柯里化定義把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)最初函數(shù)的第一個參數(shù)的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)。如果使用反柯里化,則可以這樣寫震驚某前端只會,竟月入百萬。。。 今天了解到一個新名詞:柯里化,研究一番后總結(jié)如下: 一· 柯里化 定義 把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并...
閱讀 1994·2019-08-30 15:54
閱讀 3543·2019-08-30 15:52
閱讀 1832·2019-08-29 17:20
閱讀 2527·2019-08-29 17:08
閱讀 2354·2019-08-26 13:24
閱讀 799·2019-08-26 11:59
閱讀 2788·2019-08-23 14:50
閱讀 623·2019-08-23 14:20