摘要:專題系列第十六篇,講解函數組合,并且使用柯里化和函數組合實現模式需求我們需要寫一個函數,輸入,返回。這便是函數組合。
需求JavaScript 專題系列第十六篇,講解函數組合,并且使用柯里化和函數組合實現 pointfree 模式
我們需要寫一個函數,輸入 "kevin",返回 "HELLO, KEVIN"。
嘗試var toUpperCase = function(x) { return x.toUpperCase(); }; var hello = function(x) { return "HELLO, " + x; }; var greet = function(x){ return hello(toUpperCase(x)); }; greet("kevin");
還好我們只有兩個步驟,首先小寫轉大寫,然后拼接字符串。如果有更多的操作,greet 函數里就需要更多的嵌套,類似于 fn3(fn2(fn1(fn0(x))))。
優化試想我們寫個 compose 函數:
var compose = function(f,g) { return function(x) { return f(g(x)); }; };
greet 函數就可以被優化為:
var greet = compose(hello, toUpperCase); greet("kevin");
利用 compose 將兩個函數組合成一個函數,讓代碼從右向左運行,而不是由內而外運行,可讀性大大提升。這便是函數組合。
但是現在的 compose 函數也只是能支持兩個參數,如果有更多的步驟呢?我們豈不是要這樣做:
compose(d, compose(c, compose(b, a)))
為什么我們不寫一個帥氣的 compose 函數支持傳入多個函數呢?這樣就變成了:
compose(d, c, b, a)compose
我們直接抄襲 underscore 的 compose 函數的實現:
function compose() { var args = arguments; var start = args.length - 1; return function() { var i = start; var result = args[start].apply(this, arguments); while (i--) result = args[i].call(this, result); return result; }; };
現在的 compose 函數已經可以支持多個函數了,然而有了這個又有什么用呢?
在此之前,我們先了解一個概念叫做 pointfree。
pointfreepointfree 指的是函數無須提及將要操作的數據是什么樣的。依然是以最初的需求為例:
// 需求:輸入 "kevin",返回 "HELLO, KEVIN"。 // 非 pointfree,因為提到了數據:name var greet = function(name) { return ("hello " + name).toUpperCase(); } // pointfree // 先定義基本運算,這些可以封裝起來復用 var toUpperCase = function(x) { return x.toUpperCase(); }; var hello = function(x) { return "HELLO, " + x; }; var greet = compose(hello, toUpperCase); greet("kevin");
我們再舉個稍微復雜一點的例子,為了方便書寫,我們需要借助在《JavaScript專題之函數柯里化》中寫到的 curry 函數:
// 需求:輸入 "kevin daisy kelly",返回 "K.D.K" // 非 pointfree,因為提到了數據:name var initials = function (name) { return name.split(" ").map(compose(toUpperCase, head)).join(". "); }; // pointfree // 先定義基本運算 var split = curry(function(separator, str) { return str.split(separator) }) var head = function(str) { return str.slice(0, 1) } var toUpperCase = function(str) { return str.toUpperCase() } var join = curry(function(separator, arr) { return arr.join(separator) }) var map = curry(function(fn, arr) { return arr.map(fn) }) var initials = compose(join("."), map(compose(toUpperCase, head)), split(" ")); initials("kevin daisy kelly");
從這個例子中我們可以看到,利用柯里化(curry)和函數組合 (compose) 非常有助于實現 pointfree。
也許你會想,這種寫法好麻煩吶,我們還需要定義那么多的基礎函數……可是如果有工具庫已經幫你寫好了呢?比如 ramda.js:
// 使用 ramda.js var initials = R.compose(R.join("."), R.map(R.compose(R.toUpper, R.head)), R.split(" "));
而且你也會發現:
Pointfree 的本質就是使用一些通用的函數,組合出各種復雜運算。上層運算不要直接操作數據,而是通過底層函數去處理。即不使用所要處理的值,只合成運算過程。
那么使用 pointfree 模式究竟有什么好處呢?
實戰pointfree 模式能夠幫助我們減少不必要的命名,讓代碼保持簡潔和通用,更符合語義,更容易復用,測試也變得輕而易舉。
這個例子來自于 Favoring Curry:
假設我們從服務器獲取這樣的數據:
var data = { result: "SUCCESS", tasks: [ {id: 104, complete: false, priority: "high", dueDate: "2013-11-29", username: "Scott", title: "Do something", created: "9/22/2013"}, {id: 105, complete: false, priority: "medium", dueDate: "2013-11-22", username: "Lena", title: "Do something else", created: "9/22/2013"}, {id: 107, complete: true, priority: "high", dueDate: "2013-11-22", username: "Mike", title: "Fix the foo", created: "9/22/2013"}, {id: 108, complete: false, priority: "low", dueDate: "2013-11-15", username: "Punam", title: "Adjust the bar", created: "9/25/2013"}, {id: 110, complete: false, priority: "medium", dueDate: "2013-11-15", username: "Scott", title: "Rename everything", created: "10/2/2013"}, {id: 112, complete: true, priority: "high", dueDate: "2013-11-27", username: "Lena", title: "Alter all quuxes", created: "10/5/2013"} ] };
我們需要寫一個名為 getIncompleteTaskSummaries 的函數,接收一個 username 作為參數,從服務器獲取數據,然后篩選出這個用戶的未完成的任務的 ids、priorities、titles、和 dueDate 數據,并且按照日期升序排序。
以 Scott 為例,最終篩選出的數據為:
[ {id: 110, title: "Rename everything", dueDate: "2013-11-15", priority: "medium"}, {id: 104, title: "Do something", dueDate: "2013-11-29", priority: "high"} ]
普通的方式為:
// 第一版 過程式編程 var fetchData = function() { // 模擬 return Promise.resolve(data) }; var getIncompleteTaskSummaries = function(membername) { return fetchData() .then(function(data) { return data.tasks; }) .then(function(tasks) { return tasks.filter(function(task) { return task.username == membername }) }) .then(function(tasks) { return tasks.filter(function(task) { return !task.complete }) }) .then(function(tasks) { return tasks.map(function(task) { return { id: task.id, dueDate: task.dueDate, title: task.title, priority: task.priority } }) }) .then(function(tasks) { return tasks.sort(function(first, second) { var a = first.dueDate, b = second.dueDate; return a < b ? -1 : a > b ? 1 : 0; }); }) .then(function(task) { console.log(task) }) }; getIncompleteTaskSummaries("Scott")
如果使用 pointfree 模式:
// 第二版 pointfree 改寫 var fetchData = function() { return Promise.resolve(data) }; // 編寫基本函數 var prop = curry(function(name, obj) { return obj[name]; }); var propEq = curry(function(name, val, obj) { return obj[name] === val; }); var filter = curry(function(fn, arr) { return arr.filter(fn) }); var map = curry(function(fn, arr) { return arr.map(fn) }); var pick = curry(function(args, obj){ var result = {}; for (var i = 0; i < args.length; i++) { result[args[i]] = obj[args[i]] } return result; }); var sortBy = curry(function(fn, arr) { return arr.sort(function(a, b){ var a = fn(a), b = fn(b); return a < b ? -1 : a > b ? 1 : 0; }) }); var getIncompleteTaskSummaries = function(membername) { return fetchData() .then(prop("tasks")) .then(filter(propEq("username", membername))) .then(filter(propEq("complete", false))) .then(map(pick(["id", "dueDate", "title", "priority"]))) .then(sortBy(prop("dueDate"))) .then(console.log) }; getIncompleteTaskSummaries("Scott")
如果直接使用 ramda.js,你可以省去編寫基本函數:
// 第三版 使用 ramda.js var fetchData = function() { return Promise.resolve(data) }; var getIncompleteTaskSummaries = function(membername) { return fetchData() .then(R.prop("tasks")) .then(R.filter(R.propEq("username", membername))) .then(R.filter(R.propEq("complete", false))) .then(R.map(R.pick(["id", "dueDate", "title", "priority"]))) .then(R.sortBy(R.prop("dueDate"))) .then(console.log) }; getIncompleteTaskSummaries("Scott")
當然了,利用 compose,你也可以這樣寫:
// 第四版 使用 compose var fetchData = function() { return Promise.resolve(data) }; var getIncompleteTaskSummaries = function(membername) { return fetchData() .then(R.compose( console.log, R.sortBy(R.prop("dueDate")), R.map(R.pick(["id", "dueDate", "title", "priority"]) ), R.filter(R.propEq("complete", false)), R.filter(R.propEq("username", membername)), R.prop("tasks"), )) }; getIncompleteTaskSummaries("Scott")
compose 是從右到左依此執行,當然你也可以寫一個從左到右的版本,但是從右向左執行更加能夠反映數學上的含義。
ramda.js 提供了一個 R.pipe 函數,可以做的從左到右,以上可以改寫為:
// 第五版 使用 R.pipe var getIncompleteTaskSummaries = function(membername) { return fetchData() .then(R.pipe( ), R.prop("tasks"), R.filter(R.propEq("username", membername)), R.filter(R.propEq("complete", false)), R.map(R.pick(["id", "dueDate", "title", "priority"]) R.sortBy(R.prop("dueDate")), console.log, )) };專題系列
JavaScript專題系列目錄地址:https://github.com/mqyqingfeng/Blog。
JavaScript專題系列預計寫二十篇左右,主要研究日常開發中一些功能點的實現,比如防抖、節流、去重、類型判斷、拷貝、最值、扁平、柯里、遞歸、亂序、排序等,特點是研(chao)究(xi) underscore 和 jQuery 的實現方式。
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/85144.html
摘要:專題系列共計篇,主要研究日常開發中一些功能點的實現,比如防抖節流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點是研究專題之函數組合專題系列第十六篇,講解函數組合,并且使用柯里化和函數組合實現模式需求我們需要寫一個函數,輸入,返回。 JavaScript 專題之從零實現 jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實現一個 jQuery 的 ext...
摘要:寫在前面專題系列是我寫的第二個系列,第一個系列是深入系列。專題系列自月日發布第一篇文章,到月日發布最后一篇,感謝各位朋友的收藏點贊,鼓勵指正。 寫在前面 JavaScript 專題系列是我寫的第二個系列,第一個系列是 JavaScript 深入系列。 JavaScript 專題系列共計 20 篇,主要研究日常開發中一些功能點的實現,比如防抖、節流、去重、類型判斷、拷貝、最值、扁平、柯里...
摘要:為此決定自研一個富文本編輯器。例如當要轉化的對象有環存在時子節點屬性賦值了父節點的引用,為了關于函數式編程的思考作者李英杰,美團金融前端團隊成員。只有正確使用作用域,才能使用優秀的設計模式,幫助你規避副作用。 JavaScript 專題之惰性函數 JavaScript 專題系列第十五篇,講解惰性函數 需求 我們現在需要寫一個 foo 函數,這個函數返回首次調用時的 Date 對象,注意...
摘要:深入之繼承的多種方式和優缺點深入系列第十五篇,講解各種繼承方式和優缺點。對于解釋型語言例如來說,通過詞法分析語法分析語法樹,就可以開始解釋執行了。 JavaScript深入之繼承的多種方式和優缺點 JavaScript深入系列第十五篇,講解JavaScript各種繼承方式和優缺點。 寫在前面 本文講解JavaScript各種繼承方式和優缺點。 但是注意: 這篇文章更像是筆記,哎,再讓我...
閱讀 2337·2019-08-30 15:44
閱讀 1260·2019-08-30 13:01
閱讀 3307·2019-08-30 11:22
閱讀 3093·2019-08-29 15:23
閱讀 1614·2019-08-29 12:22
閱讀 3366·2019-08-26 13:58
閱讀 3439·2019-08-26 12:17
閱讀 3479·2019-08-26 12:16