摘要:函數(shù)柯里化是把支持多個參數(shù)的函數(shù)變成接收單一參數(shù)的函數(shù),并返回一個函數(shù)能接收處理剩余參數(shù),而反柯里化就是把參數(shù)全部釋放出來。但在一些復雜的業(yè)務(wù)邏輯封裝中,函數(shù)柯里化能夠為我們提供更好的應(yīng)對方案,讓我們的函數(shù)更具自由度和靈活性。
柯里化(Curring, 以邏輯學家Haskell Curry命名)寫在開頭
柯里化理解的基礎(chǔ)來源于我們前幾篇文章構(gòu)建的知識,如果還未能掌握閉包,建議回閱前文。
代碼例子會用到 apply/call ,一般用來實現(xiàn)對象冒充,例如字符串冒充數(shù)組對象,讓字符串擁有數(shù)組的方法。待對象講解篇會細分解析。在此先了解,兩者功能相同,區(qū)別在于參數(shù)傳遞方式的不同, apply 參數(shù)以數(shù)組方式傳遞,call 多個參數(shù)則是逗號隔開。
apply(context, [arguments]); call(context, arg1, arg2, arg3, ....);
代碼例子中使用到了ES6語法,對ES6還不熟悉的話,可學習社區(qū)這篇文章:《30分鐘掌握ES6/ES2015核心內(nèi)容(上)》
函數(shù)柯里化函數(shù)柯里化在JavaScript中其實是高階函數(shù)的一種應(yīng)用,上篇文章我們簡略介紹了高階函數(shù)(可以作為參數(shù)傳遞,或作為返回值)。
理論知識太枯燥,來個生活小例子,"存款買房"(富二代繞道)。假設(shè)買房是我們存錢的終極目標。那么在買房前,存在卡里的錢(老婆本)就不能動。等到夠錢買房了,錢從銀行卡取出來,開始買買買。。。
函數(shù)柯里化就像我們往卡里存錢,存夠了,才能執(zhí)行買房操作,存不夠,接著存。
函數(shù)柯里化公式先上幾個公式(左邊是普通函數(shù),右邊就是轉(zhuǎn)化后柯里化函數(shù)支持的調(diào)用方式):
// 公式類型一 fn(a,b,c,d) => fn(a)(b)(c)(d); fn(a,b,c,d) => fn(a, b)(c)(d); fn(a,b,c,d) => fn(a)(b,c,d); // 公式類型二 fn(a,b,c,d) => fn(a)(b)(c)(d)(); fn(a,b,c,d) => fn(a);fn(b);fn(c);fn(d);fn();
兩種公式類型的區(qū)別 —— 函數(shù)觸發(fā)執(zhí)行的機制不同:
公式一當傳入?yún)?shù)等于函數(shù)參數(shù)數(shù)量時開始執(zhí)行
公式二當沒有參數(shù)傳入時(且參數(shù)數(shù)量滿足)開始執(zhí)行
通過公式,我們先來理解這行代碼 fn(a)(b)(c)(d), 執(zhí)行 fn(a) 時返回的是一個函數(shù),并且支持傳參。何時返回目標函數(shù)結(jié)果值而不是函數(shù)的觸發(fā)機制,控制權(quán)在我們手里,我們可以為函數(shù)制定不同的觸發(fā)機制。
普通的函數(shù)調(diào)用,一次性傳入?yún)?shù)就執(zhí)行。而通過柯里化,它可以幫我們實現(xiàn)函數(shù)部分參數(shù)傳入執(zhí)行(并未立即執(zhí)行原始函數(shù),錢沒存夠接著存),這就是函數(shù)柯里化的特點:"延遲執(zhí)行和部分求值"
"函數(shù)柯里化:指封裝一個函數(shù),接收原始函數(shù)作為參數(shù)傳入,并返回一個能夠接收并處理剩余參數(shù)的函數(shù)"
函數(shù)柯里化的例子// 等待我們柯里化實現(xiàn)的方法add function add(a, b, c, d) { return a + b + c + d; };
// 最簡單地實現(xiàn)函數(shù)add的柯里化 // 有點low,有助于理解 function add(a, b, c, d) { return function(a) { return function(b) { return function(c) { return a + b + c + d; } } } }
分析代碼知識點:
函數(shù)作為返回值返回,閉包形成,外部環(huán)境可訪問函數(shù)內(nèi)部作用域
子函數(shù)可訪問父函數(shù)的作用域,作用域由內(nèi)而外的作用域鏈查找規(guī)則,作用域嵌套形成
在函數(shù)參數(shù)數(shù)量不滿足時,返回一個函數(shù)(該函數(shù)可接收并處理剩余參數(shù))
當函數(shù)數(shù)量滿足我們的觸發(fā)機制(可自由制定),觸發(fā)原始函數(shù)執(zhí)行
前幾篇文章的知識點此時剛好??梢娀A(chǔ)知識的重要性,高階的東西始終要靠小磚頭堆砌出來。
弄清原理后,接下來就是將代碼寫得更通用些(高大上些)。
// 公式類型一: 參數(shù)數(shù)量滿足函數(shù)參數(shù)要求,觸發(fā)執(zhí)行 // fn(a,b,c,d) => fn(a)(b)(c)(d); const createCurry = (fn, ...args) => { let _args = args || []; let length = fn.length; // fn.length代碼函數(shù)參數(shù)數(shù)量 return (...rest) => { let _allArgs = _args.slice(0); // 深拷貝閉包共用對象_args,避免后續(xù)操作影響(引用類型) _allArgs.push(...rest); if (_allArgs.length < length) { // 參數(shù)數(shù)量不滿足原始函數(shù)數(shù)量,返回curry函數(shù) return createCurry.call(this, fn, ..._allArgs); } else { // 參數(shù)數(shù)量滿足原始函數(shù)數(shù)量,觸發(fā)執(zhí)行 return fn.apply(this, _allArgs); } } } const curryAdd = createCurry(add, 2); let sum = curryAdd(3)(4)(5); // 14 // ES5寫法 function createCurry() { var fn = arguments[0]; var _args = [].slice.call(arguments, 1); var length = fn.length; return function() { var _allArgs = _args.slice(0); _allArgs = _allArgs.concat([].slice.call(arguments)); if (_allArgs.length < length) { _allArgs.unshift(fn); return createCurry.apply(this, _allArgs); } else { return fn.apply(this, _allArgs); } } }
// 公式類型二: 無參數(shù)傳入時并且參數(shù)數(shù)量已經(jīng)滿足函數(shù)要求 // fn(a, b, c, d) => fn(a)(b)(c)(d)(); // fn(a, b, c, d) => fn(a);fn(b);fn(c);fn(d);fn(); const createCurry = (fn, ...args) => { let all = args || []; let length = fn.length; return (...rest) => { let _allArgs = all.slice(0); _allArgs.push(...rest); if (rest.length > 0 || _allArgs.length < length) { // 調(diào)用時參數(shù)不為空或存儲的參數(shù)不滿足原始函數(shù)參數(shù)數(shù)量時,返回curry函數(shù) return createCurry.call(this, fn, ..._allArgs); } else { // 調(diào)用參數(shù)為空(),且參數(shù)數(shù)量滿足時,觸發(fā)執(zhí)行 return fn.apply(this, _allArgs); } } } const curryAdd = createCurry(add, 2); let sum = curryAdd(3)(4)(5)(); // 14 // ES5寫法 function createCurry() { var fn = arguments[0]; var _args = [].slice.call(arguments, 1); var length = fn.length; return function() { var _allArgs = _args.slice(0); _allArgs = _allArgs.concat([].slice.call(arguments)); if (arguments.length > 0 || _allArgs.length < length) { _allArgs.unshift(fn); return createCurry.apply(this, _allArgs); } else { return fn.apply(this, _allArgs); } } }
為實現(xiàn)公式中不同的兩種調(diào)用公式,兩個createCurry方法制定了兩種不同的觸發(fā)機制。記住一個點,函數(shù)觸發(fā)機制可根據(jù)需求自行制定。
偏函數(shù)與柯里化的區(qū)別先上個公式看對比:
// 函數(shù)柯里化:參數(shù)數(shù)量完整 fn(a,b,c,d) => fn(a)(b)(c)(d); fn(a,b,c,d) => fn(a,b)(c)(d); // 偏函數(shù):只執(zhí)行了部分參數(shù) fn(a,b,c,d) => fn(a); fn(a,b,c,d) => fn(a, b);
"函數(shù)柯里化中,當你傳入部分參數(shù)時,返回的并不是原始函數(shù)的執(zhí)行結(jié)果,而是一個可以繼續(xù)支持后續(xù)參數(shù)的函數(shù)。而偏函數(shù)的調(diào)用方式更像是普通函數(shù)的調(diào)用方式,只調(diào)用一次,它通過原始函數(shù)內(nèi)部來實現(xiàn)不定參數(shù)的支持。"
如果已經(jīng)看懂上述柯里化的代碼例子,那么改寫支持偏函數(shù)的代碼,并不難。
// 公式: // fn(a, b, c, d) => fn(a); // fn(a, b, c, d) => fn(a,b,c); const partialAdd = (a = 0, b = 0, c = 0, d = 0) => { return a + b + c +d; } partialAdd(6); // 6 partialAdd(2, 3); // 5
使用ES6函數(shù)參數(shù)默認值,為沒有傳入?yún)?shù),指定默認值為0,支持無參數(shù)或不定參數(shù)傳入。
柯里化的特點:參數(shù)復用(固定易變因素)
延遲執(zhí)行
提前返回
柯里化的缺點柯里化是犧牲了部分性能來實現(xiàn)的,可能帶來的性能損耗:
存取 arguments 對象要比存取命名參數(shù)要慢一些
老版本瀏覽器在 arguments.lengths 的實現(xiàn)相當慢(新版本瀏覽器忽略)
fn.apply() 和 fn.call() 要比直接調(diào)用 fn() 慢
大量嵌套的作用域和閉包會帶來開銷,影響內(nèi)存占用和作用域鏈查找速度
柯里化的應(yīng)用利用柯里化制定約束條件,管控觸發(fā)機制
處理瀏覽器兼容(參數(shù)復用實現(xiàn)一次性判斷)
函數(shù)節(jié)流防抖(延遲執(zhí)行)
ES5前bind方法的實現(xiàn)
一個應(yīng)用例子:瀏覽器事件綁定的兼容處理// 普通事件綁定函數(shù) var addEvent = function(ele, type, fn, isCapture) { if(window.addEventListener) { ele.addEventListener(type, fn, isCapture) } else if(window.attachEvent) { ele.attachEvent("on" + type, fn) } } // 弊端:每次調(diào)用addEvent都會進行判斷 // 柯里化事件綁定函數(shù) var addEvent = (function() { if(window.addEventListener) { return function(ele, type, fn, isCapture) { ele.addEventListener(type, fn, isCapture) } } else if(window.attachEvent) { return function(ele, type, fn) { ele.attachEvent("on" + type, fn) } } })() // 優(yōu)勢:判斷只執(zhí)行一次,通過閉包保留了父級作用域的判斷結(jié)果秒懂反柯里化
先上公式,從來沒有這么喜歡寫公式,簡明易懂。
// 反柯里化公式: curryFn(a)(b)(c)(d) = fn(a, b, c, d); curryFn(a) = fn(a);
看完公式,是不是似曾相識,這不就是我們?nèi)粘G么a的普通函數(shù)么?沒錯的,函數(shù)柯里化就是把普通函數(shù)變成成一個復雜的函數(shù),而反柯里化其就是柯里化的逆反,把復雜變得簡單。
函數(shù)柯里化是把支持多個參數(shù)的函數(shù)變成接收單一參數(shù)的函數(shù),并返回一個函數(shù)能接收處理剩余參數(shù):fn(a,b,c,d) => fn(a)(b)(c)(d),而反柯里化就是把參數(shù)全部釋放出來:fn(a)(b)(c)(d) => fn(a,b,c,d)。
// 反柯里化:最簡單的反柯里化(普通函數(shù)) function add(a, b, c, d) { return a + b + c + d; }反思:為何要使用柯里化
函數(shù)柯里化是函數(shù)編程中的一個重要的基礎(chǔ),它為我們提供了一種編程的思維方式。顯然,它讓我們的函數(shù)處理變得復雜,代碼調(diào)用方式并不直觀,還加入了閉包,多層作用域嵌套,會有一些性能上的影響。
但在一些復雜的業(yè)務(wù)邏輯封裝中,函數(shù)柯里化能夠為我們提供更好的應(yīng)對方案,讓我們的函數(shù)更具自由度和靈活性。
實際開發(fā)中,如果你的邏輯處理相對復雜,不妨換個思維,用函數(shù)柯里化來實現(xiàn),技能包不嫌多。
說到底,程序員就是解決問題的那群人。
本篇函數(shù)柯里化知識點的理解確實存在難度,暫時跳過這章也無妨,可以先了解再深入。耐得主寂寞的小伙伴回頭多啃幾遍,沒準春季面試就遇到了。
參考文檔:
js高階函數(shù)應(yīng)用—函數(shù)柯里化和反柯里化
前端基礎(chǔ)進階(八):深入詳解函數(shù)的柯里化
系列更文請關(guān)注專欄:《前端進擊的巨人》,不斷更新中。。。
本文首發(fā)Github,期待Star!
https://github.com/ZengLingYong/blog
作者:以樂之名
本文原創(chuàng),有不當?shù)牡胤綒g迎指出。轉(zhuǎn)載請指明出處。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/101329.html
摘要:有關(guān)函數(shù)柯里化的詳解,請回閱前端進擊的巨人五學會函數(shù)柯里化。構(gòu)造函數(shù)中的通過操作符可以實現(xiàn)對函數(shù)的構(gòu)造調(diào)用。在了解構(gòu)造函數(shù)中的前,有必要先了解下實例化對象的過程。 showImg(https://segmentfault.com/img/bVburMp?w=800&h=600); 常見this的誤解 指向函數(shù)自身(源于this英文意思的誤解) 指向函數(shù)的詞法作用域(部分情況) th...
摘要:所以下面介紹一些函數(shù)式編程的知識和概念。函數(shù)式編程的一個明顯的好處就是這種聲明式的代碼,對于無副作用的純函數(shù),我們完全可以不考慮函數(shù)內(nèi)部是如何實現(xiàn)的,專注于編寫業(yè)務(wù)代碼。我會在下一篇文章中介紹函數(shù)式編程的更加高階一些的知識,例如等等概念。 一、引言 說到函數(shù)式編程,大家可能第一印象都是學院派的那些晦澀難懂的代碼,充滿了一大堆抽象的不知所云的符號,似乎只有大學里的計算機教授才會使用這些東...
摘要:原題如下寫一個方法,當使用下面的語法調(diào)用時,能正常工作這道題要考察的,就是對函數(shù)柯里化的理解。當參數(shù)只有一個的時候,進行柯里化的處理。這其實就是函數(shù)柯里化的簡單應(yīng)用。 showImg(https://segmentfault.com/img/bVbopGm?w=620&h=350); 前言 這是前端面試題系列的第 6 篇,你可能錯過了前面的篇章,可以在這里找到: ES6 中箭頭函數(shù)的...
摘要:作為函數(shù)式編程語言,帶來了很多語言上的有趣特性,比如柯里化和反柯里化。個人理解不知道對不對延遲執(zhí)行柯里化的另一個應(yīng)用場景是延遲執(zhí)行。不斷的柯里化,累積傳入的參數(shù),最后執(zhí)行。 作為函數(shù)式編程語言,JS帶來了很多語言上的有趣特性,比如柯里化和反柯里化。 這里可以對照另外一篇介紹 JS 反柯里化 的文章一起看~ 1. 簡介 柯里化(Currying),又稱部分求值(Partial Evalu...
摘要:作為函數(shù)式編程語言,帶來了很多語言上的有趣特性,比如柯里化和反柯里化。在一些函數(shù)式編程語言中,會定義一個特殊的占位變量。個人理解不知道對不對延遲執(zhí)行柯里化的另一個應(yīng)用場景是延遲執(zhí)行。不斷的柯里化,累積傳入的參數(shù),最后執(zhí)行。作為函數(shù)式編程語言,JS帶來了很多語言上的有趣特性,比如柯里化和反柯里化。 這里可以對照另外一篇介紹 JS 反柯里化 的文章一起看~ 1. 簡介 柯里化(Currying)...
閱讀 3389·2021-11-23 09:51
閱讀 1079·2021-09-26 09:55
閱讀 4101·2021-09-22 14:58
閱讀 1767·2021-09-08 09:35
閱讀 1156·2021-08-26 14:16
閱讀 950·2019-08-23 18:17
閱讀 2178·2019-08-23 16:45
閱讀 760·2019-08-23 15:55