JavaScript 专题之偏函数
维基百科中对偏函数(Partial application)的定义为:
In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.
翻译成中文:
在计算机科学中,局部应用是指固定一个函数的一些参数,然后产生另一个更小元的函数。什么是元?元是指函数参数的个数,比如一个带有两个参数的函数被称为二元函数。
举个简单的例子:
function add(a, b) { return a + b; } // 执行 add 函数,一次传入两个参数即可 add(1, 2) // 3 // 假设有一个 partial 函数可以做到局部应用 var addOne = partial(add, 1); addOne(2) // 3
个人觉得翻译成“局部应用”或许更贴切些,以下全部使用“局部应用”。
柯里化与局部应用
实际上你会发现这个例子和柯里化太像了,所以两者到底是有什么区别呢?其实也很明显:
柯里化是将一个多参数函数转换成多个单参数函数,也就是将一个 n 元函数转换成 n 个一元函数。
局部应用则是固定一个函数的一个或者多个参数,也就是将一个 n 元函数转换成一个 n - x 元函数。
如果说两者有什么关系的话,引用 functional-programming-jargon 中的描述就是:
Curried functions are automatically partially applied.
partial
我们今天的目的是模仿 underscore 写一个 partial 函数,比起 curry 函数,这个显然简单了很多。
也许你在想我们可以直接使用 bind 呐,举个例子:
function add(a, b) { return a + b; } var addOne = add.bind(null, 1); addOne(2) // 3
然而使用 bind 我们还是改变了 this 指向,我们要写一个不改变 this 指向的方法。
第一版
根据之前的表述,我们可以尝试着写出第一版:
// 第一版 // 似曾相识的代码 function partial(fn) { var args = [].slice.call(arguments, 1); return function() { var newArgs = args.concat([].slice.call(arguments)); return fn.apply(this, newArgs); }; };
我们来写个 demo 验证下 this 的指向:
function add(a, b) { return a + b + this.value; } // var addOne = add.bind(null, 1); var addOne = partial(add, 1); var value = 1; var obj = { value: 2, addOne: addOne } obj.addOne(2); // ??? // 使用 bind 时,结果为 4 // 使用 partial 时,结果为 5
第二版
然而正如 curry 函数可以使用占位符一样,我们希望 partial 函数也可以实现这个功能,我们再来写第二版:
// 第二版 var _ = {}; function partial(fn) { var args = [].slice.call(arguments, 1); return function() { var position = 0, len = args.length; for(var i = 0; i < len; i++) { args[i] = args[i] === _ ? arguments[position++] : args[i] } while(position < arguments.length) args.push(arguments[position++]); return fn.apply(this, args); }; };
我们验证一下:
var subtract = function(a, b) { return b - a; }; subFrom20 = partial(subtract, _, 20); subFrom20(5);
写在最后
值得注意的是:underscore 和 lodash 都提供了 partial 函数,但只有 lodash 提供了 curry 函数。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: JavaScript 专题之 惰性函数
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(22)
while(position < arguments.length) args.push(arguments[position++])
应该改为:
while(position < arguments.length) args[position]=(arguments[position++]);
@kashtian
@cyhwinner
不一样吧,原文的意思是:将arguments[position]这个值放入到args中;你改后的意思是:将arguments从position位置截取到数组最后一项的所有值放入到args中;原文放入了一个值,你放入了从position位置到最后一个位置的好几个值
强烈建议大佬把博客文章整理一下搞个类似阮一峰老师的ES6微信小程序
您好,我最近一直在学习您编写的文章,在这一章中我实在分辨不出柯里化和偏函数的区别,还有能说明下这两者的应用场景吗?
这段就是bind的实现呀 所以可以理解成 Function.prototype.bind函数也是一个偏函数啦
你可能没有注意到 第二个替换 while 改成了 if
直接换成 下面这样会不会好点
占位符用在里面有什么用
你写错了吧。没有被污染啊
es6简化版
大佬,这个偏函数是不是有点问题,subFrom20(5),subFrom20(4)都输出15,那个args被污染拉,是不是该拷贝一份
var _ = {};
args[i] === _
这样写是有什么奥妙么
那这样说来
Function.prototype.bind
也是偏函数咯可不可以这样理解,偏函数的一种特殊情况就是curry
@evanzlj 我现在维护三个平台就已经有点忙不过来啦,公众号就更麻烦了,而且我觉得还是 PC 端更适合学习一些,可以边看文章边动手试验代码。
大佬开个公众号吧,这样可以有更大的动力~~ 赏你一杯咖啡,哈哈~~
总结得很棒!
@SilenceZeng 非常感谢指出~ 已修改~ o( ̄▽ ̄)d
第二版 while(position < arguments.length) args.push(argumetns[position++]); 中后一个arguments拼成argumetns了
@Syzq 没有哎~
大佬有公众号吗