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