Haskell和scala都支援函數的柯里化,JavaScript函數的柯里化也與JavaScript的函數程式設計有很大的連結,如果你有興趣的話,可以在這些方面多下功夫了解,相信收穫一定很多.
看本篇文章需要知道的一些知識點
- 函數部分的
call
/apply
/arguments
- 閉包
- 高階函數
- #不完全函數
文章後面有對這些知識的簡單解釋,大家可以看看.
什麼是柯里化?
我們先來看看維基百科中是如何定義的:在計算機科學中,柯里化(英語:Currying),又譯為卡瑞化或加里化,是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受餘下的參數而且傳回結果的新函數的技術。
我們可以舉個簡單的例子,如下函數add
是一般的一個函數,就是將傳進來的參數a
和b
相加;函數curryingAdd
就是對函數add
進行柯里化的函數;
這樣一來,原來我們需要直接傳進去兩個參數來運算的函數,現在需要分別傳入參數a
和b
,函數如下:
function add(a, b) { return a + b; } function curryingAdd(a) { return function(b) { return a + b; } } add(1, 2); // 3 curryingAdd(1)(2); // 3
看到這裡你可能會想,這樣做有什麼用?為什麼要這樣做?這樣做能夠為我們的應用帶來什麼樣的好處?先別著急,我們接著往下看.
為什麼要對函數進行柯里化?
- 可以使用一些小技巧(見下文)
- 提前綁定好函數裡面的某些參數,達到參數復用的效果,提高了適用性.
- 固定易變因素
- 延遲計算
總之,函數的柯里化能夠讓你重新組合你的應用,把你的複雜功能拆分成一個一個的小部分,每一個小的部分都是簡單的,便於理解的,而且是容易測試的;
如何對函數進行柯里化?
在這一部分裡,我們由淺入深的一步步來告訴大家如何對一個多參數的函數進行柯里化.其中用到的知識有閉包
,高階函數
,不完全函數
等等.
-
I 開胃菜
假如我們要實現一個功能,就是輸出語句
name
喜歡song
,其中name
和song
都是可變參數;那麼一般情況下我們會這樣寫:function printInfo(name, song) { console.log(name + '喜欢的歌曲是: ' + song); } printInfo('Tom', '七里香'); printInfo('Jerry', '雅俗共赏');
對上面的函數進行柯里化之後,我們可以這樣寫:
function curryingPrintInfo(name) { return function(song) { console.log(name + '喜欢的歌曲是: ' + song); } } var tomLike = curryingPrintInfo('Tom'); tomLike('七里香'); var jerryLike = curryingPrintInfo('Jerry'); jerryLike('雅俗共赏');
-
II 小雞燉蘑菇
#上面我們雖然對對函數
printInfo
進行了柯里化,但是我們可不想在需要柯里化的時候,都像上面那樣不斷地進行函數的嵌套,那簡直是噩夢;
所以我們要創造一些幫助其它函數進行柯里化的函數,我們暫且叫它為curryingHelper
吧,一個簡單的curryingHelper
函數如下:function curryingHelper(fn) { var _args = Array.prototype.slice.call(arguments, 1); return function() { var _newArgs = Array.prototype.slice.call(arguments); var _totalArgs = _args.concat(_newArgs); return fn.apply(this, _totalArgs); } }
這裡解釋一點東西,首先函數的
arguments
表示的是傳遞到函數中的參數物件,它不是一個陣列,它是一個類別數組物件;
所以我們可以使用函數的Array.prototype.slice
方法,然後使用.call
方法來取得arguments
裡面的內容.
我們使用fn.apply(this, _totalArgs)
來給函數fn
#傳遞正確的參數.接下來我們來寫一個簡單的函數驗證上面的輔助柯里化函數的正確性, 程式碼部分如下:
function showMsg(name, age, fruit) { console.log('My name is ' + name + ', I\'m ' + age + ' years old, ' + ' and I like eat ' + fruit); } var curryingShowMsg1 = curryingHelper(showMsg, 'dreamapple'); curryingShowMsg1(22, 'apple'); // My name is dreamapple, I'm 22 years old, and I like eat apple var curryingShowMsg2 = curryingHelper(showMsg, 'dreamapple', 20); curryingShowMsg2('watermelon'); // My name is dreamapple, I'm 20 years old, and I like eat watermelon
上面的結果表示,我們的這個柯裡化的函數是正確的.上面的
curryingHelper
就是一個高階函數,關於高階函數的解釋可以參考下文. -
III 牛肉火鍋
上面的柯里化幫助函數確實已經能夠達到我們的一般性需求了,但是它還不夠好,我們希望那些經過柯里化後的函數可以每次只傳遞進去一個參數,
然後可以進行多次參數的傳遞,那麼應該怎麼做呢?我們可以再花費一些腦筋,寫出一個betterCurryingHelper
函數,實現我們上面說的那些
功能.代碼如下:function betterCurryingHelper(fn, len) { var length = len || fn.length; return function () { var allArgsFulfilled = (arguments.length >= length); // 如果参数全部满足,就可以终止递归调用 if (allArgsFulfilled) { return fn.apply(this, arguments); } else { var argsNeedFulfilled = [fn].concat(Array.prototype.slice.call(arguments)); return betterCurryingHelper(curryingHelper.apply(this, argsNeedFulfilled), length - arguments.length); } }; }
其中
curryingHelper
就是上面II 小雞燉蘑菇中提及的那個函數.需要注意的是fn.length
表示的是這個函數的參數長度.
接下來我們來檢定這個函數的正確性:var betterShowMsg = betterCurryingHelper(showMsg); betterShowMsg('dreamapple', 22, 'apple'); // My name is dreamapple, I'm 22 years old, and I like eat apple betterShowMsg('dreamapple', 22)('apple'); // My name is dreamapple, I'm 22 years old, and I like eat apple betterShowMsg('dreamapple')(22, 'apple'); // My name is dreamapple, I'm 22 years old, and I like eat apple betterShowMsg('dreamapple')(22)('apple'); // My name is dreamapple, I'm 22 years old, and I like eat apple
其中
showMsg
就是II 小雞燉蘑菇部分提及的那個函數.
我們可以看出來,這個betterCurryingHelper
確實實現了我們想要的那個功能.並且我們也可以像使用原來的那個函數一樣使用柯里化後的函數. -
IV 泡椒鳳爪
我们已经能够写出很好的柯里化辅助函数了,但是这还不算是最刺激的,如果我们在传递参数的时候可以不按照顺来那一定很酷;当然我们也可以写出这样的函数来,
这个crazyCurryingHelper
函数如下所示:var _ = {}; function crazyCurryingHelper(fn, length, args, holes) { length = length || fn.length; args = args || []; holes = holes || []; return function() { var _args = args.slice(), _holes = holes.slice(); // 存储接收到的args和holes的长度 var argLength = _args.length, holeLength = _holes.length; var allArgumentsSpecified = false; // 循环 var arg = null, i = 0, aLength = arguments.length; for(; i = length); if(allArgumentsSpecified) { return fn.apply(this, _args); } // 递归的进行柯里化 return crazyCurryingHelper.call(this, fn, length, _args, _holes); }; }
一些解释,我们使用
_
来表示参数中的那些缺失的参数,如果你使用了lodash的话,会有冲突的;那么你可以使用别的符号替代.
按照一贯的尿性,我们还是要验证一下这个crazyCurryingHelper
是不是实现了我们所说的哪些功能,代码如下:var crazyShowMsg = crazyCurryingHelper(showMsg); crazyShowMsg(_, 22)('dreamapple')('apple'); // My name is dreamapple, I'm 22 years old, and I like eat apple crazyShowMsg( _, 22, 'apple')('dreamapple'); // My name is dreamapple, I'm 22 years old, and I like eat apple crazyShowMsg( _, 22, _)('dreamapple', _, 'apple'); // My name is dreamapple, I'm 22 years old, and I like eat apple crazyShowMsg( 'dreamapple', _, _)(22)('apple'); // My name is dreamapple, I'm 22 years old, and I like eat apple crazyShowMsg('dreamapple')(22)('apple'); // My name is dreamapple, I'm 22 years old, and I like eat apple
结果显示,我们这个函数也实现了我们所说的那些功能.
柯里化的一些应用场景
说了那么多,其实这部分才是最重要的部分;学习某个知识要一定可以用得到,不然学习它干嘛.
-
关于函数柯里化的一些小技巧
-
给
setTimeout
传递地进来的函数添加参数一般情况下,我们如果想给一个
setTimeout
传递进来的函数添加参数的话,一般会使用之种方法:function hello(name) { console.log('Hello, ' + name); } setTimeout(hello('dreamapple'), 3600); //立即执行,不会在3.6s后执行 setTimeout(function() { hello('dreamapple'); }, 3600); // 3.6s 后执行
我们使用了一个新的匿名函数包裹我们要执行的函数,然后在函数体里面给那个函数传递参数值.
当然,在ES5里面,我们也可以使用函数的
bind
方法,如下所示:setTimeout(hello.bind(this, 'dreamapple'), 3600); // 3.6s 之后执行函数
这样也是非常的方便快捷,并且可以绑定函数执行的上下文.
我们本篇文章是讨论函数的柯里化,当然我们这里也可以使用函数的柯里化来达到这个效果:
setTimeout(curryingHelper(hello, 'dreamapple'), 3600); // 其中curryingHelper是上面已经提及过的
这样也是可以的,是不是很酷.其实函数的
bind
方法也是使用函数的柯里化来完成的,详情可以看这里Function.prototype.bind(). -
写出这样一个函数
multiply(1)(2)(3) == 6
结果为true
,multiply(1)(2)(3)(...)(n) == (1)*(2)*(3)*(...)*(n)
结果为true
这个题目不知道大家碰到过没有,不过通过函数的柯里化,也是有办法解决的,看下面的代码:
function multiply(x) { var y = function(x) { return multiply(x * y); }; y.toString = y.valueOf = function() { return x; }; return y; } console.log(multiply(1)(2)(3) == 6); // true console.log(multiply(1)(2)(3)(4)(5) == 120); // true
因为
multiply(1)(2)(3)
的直接结果并不是6,而是一个函数对象{ [Number: 6] valueOf: [Function], toString: [Function] }
,我们
之后使用了==
会将左边这个函数对象转换成为一个数字,所以就达到了我们想要的结果.还有关于为什么使用toString
和valueOf
方法
可以看看这里的解释Number.prototype.valueOf(),Function.prototype.toString(). -
上面的那个函数不够纯粹,我们也可以实现一个更纯粹的函数,但是可以会不太符合题目的要求.
我们可以这样做,先把函数的参数存储,然后再对这些参数做处理,一旦有了这个思路,我们就不难写出些面的代码:function add() { var args = Array.prototype.slice.call(arguments); var _that = this; return function() { var newArgs = Array.prototype.slice.call(arguments); var total = args.concat(newArgs); if(!arguments.length) { var result = 1; for(var i = 0; i
-
当我们的需要兼容IE9之前版本的IE浏览器的话,我们可能需要写出一些兼容的方案 ,比如事件监听;一般情况下我们应该会这样写:
var addEvent = function (el, type, fn, capture) { if (window.addEventListener) { el.addEventListener(type, fn, capture); } else { el.attachEvent('on' + type, fn); } };
这也写也是可以的,但是性能上会差一点,因为如果是在低版本的IE浏览器上每一次都会运行
if()
语句,产生了不必要的性能开销.
我们也可以这样写:var addEvent = (function () { if (window.addEventListener) { return function (el, type, fn, capture) { el.addEventListener(type, fn, capture); } } else { return function (el, type, fn) { var IEtype = 'on' + type; el.attachEvent(IEtype, fn); } } })();
这样就减少了不必要的开支,整个函数运行一次就可以了.
-
-
延迟计算
上面的那两个函数
multiply()
和add()
实际上就是延迟计算的例子. -
提前绑定好函数里面的某些参数,达到参数复用的效果,提高了适用性.
我们的
I 开胃菜
部分的tomLike
和jerryLike
其实就是属于这种的,绑定好函数里面的第一个参数,然后后面根据情况分别使用不同的函数. -
固定易变因素
我们经常使用的函数的
bind
方法就是一个固定易变因素的很好的例子.
关于柯里化的性能
当然,使用柯里化意味着有一些额外的开销;这些开销一般涉及到这些方面,首先是关于函数参数的调用,操作arguments
对象通常会比操作命名的参数要慢一点;
还有,在一些老的版本的浏览器中arguments.length
的实现是很慢的;直接调用函数fn
要比使用fn.apply()
或者fn.call()
要快一点;产生大量的嵌套
作用域还有闭包会带来一些性能还有速度的降低.但是,大多数的web应用的性能瓶颈时发生在操作DOM上的,所以上面的那些开销比起DOM操作的开销还是比较小的.
关于本章一些知识点的解释
-
琐碎的知识点
fn.length
: 表示的是这个函数中参数的个数.arguments.callee
: 指向的是当前运行的函数.callee
是arguments
对象的属性。
在该函数的函数体内,它可以指向当前正在执行的函数.当函数是匿名函数时,这是很有用的,比如没有名字的函数表达式(也被叫做"匿名函数").
详细解释可以看这里arguments.callee.我们可以看一下下面的例子:function hello() { return function() { console.log('hello'); if(!arguments.length) { console.log('from a anonymous function.'); return arguments.callee; } } } hello()(1); // hello /* * hello * from a anonymous function. * hello * from a anonymous function. */ hello()()();
fn.caller
: 返回调用指定函数的函数.详细的解释可以看这里Function.caller,下面是示例代码:function hello() { console.log('hello'); console.log(hello.caller); } function callHello(fn) { return fn(); } callHello(hello); // hello [Function: callHello]
-
高阶函数(high-order function)
高阶函数就是操作函数的函数,它接受一个或多个函数作为参数,并返回一个新的函数.
我们来看一个例子,来帮助我们理解这个概念.就举一个我们高中经常遇到的场景,如下:f1(x, y) = x + y; f2(x) = x * x; f3 = f2(f3(x, y));
我们来实现
f3
函数,看看应该如何实现,具体的代码如下所示:function f1(x, y) { return x + y; } function f2(x) { return x * x; } function func3(func1, func2) { return function() { return func2.call(this, func1.apply(this, arguments)); } } var f3 = func3(f1, f2); console.log(f3(2, 3)); // 25
我们通过函数
func3
将函数f1
,f2
结合到了一起,然后返回了一个新的函数f3
;这个函数就是我们期望的那个函数. -
不完全函数(partial function)
什么是不完全函数呢?所谓的不完全函数和我们上面所说的柯里化基本差不多;所谓的不完全函数,就是给你想要运行的那个函数绑定一个固定的参数值;
然后后面的运行或者说传递参数都是在前面的基础上进行运行的.看下面的例子:// 一个将函数的arguments对象变成一个数组的方法 function array(a, n) { return Array.prototype.slice.call(a, n || 0); } // 我们要运行的函数 function showMsg(a, b, c){ return a * (b - c); } function partialLeft(f) { var args = arguments; return function() { var a = array(args, 1); a = a.concat(array(arguments)); console.log(a); // 打印实际传递到函数中的参数列表 return f.apply(this, a); } } function partialRight(f) { var args = arguments; return function() { var a = array(arguments); a = a.concat(array(args, 1)); console.log(a); // 打印实际传递到函数中的参数列表 return f.apply(this, a); } } function partial(f) { var args = arguments; return function() { var a = array(args, 1); var i = 0; j = 0; for(; i
一些你可能会喜欢的JS库
JavaScript的柯里化与JavaScript的函数式编程密不可分,下面列举了一些关于JavaScript函数式编程的库,大家可以看一下:
- underscore
- lodash
- ramda
- bacon.js
- fn.js
- functional-js
推荐教程:《JS教程》
以上是帶你了解並掌握JavaScript函數的柯里化的詳細內容。更多資訊請關注PHP中文網其他相關文章!

是的,JavaScript的引擎核心是用C語言編寫的。 1)C語言提供了高效性能和底層控制,適合JavaScript引擎的開發。 2)以V8引擎為例,其核心用C 編寫,結合了C的效率和麵向對象特性。 3)JavaScript引擎的工作原理包括解析、編譯和執行,C語言在這些過程中發揮關鍵作用。

JavaScript是現代網站的核心,因為它增強了網頁的交互性和動態性。 1)它允許在不刷新頁面的情況下改變內容,2)通過DOMAPI操作網頁,3)支持複雜的交互效果如動畫和拖放,4)優化性能和最佳實踐提高用戶體驗。

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

PhpStorm Mac 版本
最新(2018.2.1 )專業的PHP整合開發工具

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器