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