下面是uncurring的两种实现
实现1
Function.prototype.uncurrying = function(){
var self = this;
return function(){
// 获取传入的上下文对象
var context = Array.prototype.shift.call(arguments);
// 这里的this是调用uncurrying者
return self.apply(context, arguments);
};
};
var push = Array.prototype.push.uncurrying ();
var arr = [];
push(arr, 1); // ==> arr = [1]
push(arr, 4); // ==> arr = [1, 4]
实现2
Function.prototype.uncurrying = function(){
var self = this;
return function(){
return Function.prototype.call.apply(self, arguments);
};
};
var push = Array.prototype.push.uncurrying ();
var arr = [];
push(arr, 1); // ==> arr = [1]
push(arr, 4); // ==> arr = [1, 4]
两种结果是一样的,但是第二种实现的方式我有点迷糊,主要是这里
第一种方式显示的用self,在这里也就是push方法执行了一下,
self.apply(context, arguments);
但是如下第二种实现方式,却没有发现self执行的痕迹,
按我的理解这里就是用apply修改call的上下文为self,这里也就是push,
但这样有执行push方法吗?难道call内部的实现帮忙执行了self?求解
Function.prototype.call.apply(self, arguments);
瞬间被你点通,谢谢 !
louiszhai
Function.prototype.call.apply(self, arguments);
先用apply修改了call的上下文为self,
后续调用uncurrying,相当于在self上调用call方法,也就执行了self
Function.prototype.call.apply(self, arguments);
这个看起来有些绕,其实很好理解。实际上,由你的第二种实现还可以推出反柯里化的第三种实现:
接下来我会先分析下你的第二种实现,再分析第三种实现。你的实现是这样的:
谁调用
uncurrying
,谁就等于this
或self
. 这意味着self
就是数组的push方法
.uncurrying
,谁就等于this
或self
. 这意味着self
就是数组的push方法
.替换掉
self
,最终外部的push
替换掉self
,最终外部的push
等同如下函数:函数放在这里,我们先来理解
apply
函数,apply
有分解数组为一个个参数的作用。推导公式:
a.apply(b, arguments)
意味着把b当做this上下文,相当于是在b上调用a方法,并且传入所有的参数,如果b中本身就含有a方法,那么就相当于b.a(arg1, arg2,…)
公式1:
a.apply(b, arguments) === b.a(arg1, arg2,…)
由于
call
和apply
除参数处理不一致之外,其他作用一致,那么公式可以进一步演化得到:公式2:
a.call(b, arg) === b.a(arg)
将公式1这些代入上面的函数,有:
a = Function.prototype.call
即a等于call方法。我们接着代入公式,有:
b = Array.prototype.push
即b等于数组的push方法那么
Function.prototype.call.apply(Array.prototype.push, arguments)
就相对于:Array.prototype.push.call(arg1, arg2,…)
,那么:push([], 1)
就相当于Array.prototype.push.call([], 1)
,再代入公式2,相当于:[].push(1)
答案已经呼之欲出了,就是往数组中末尾添加数字1。
接下来我来分析反柯里化的第三种实现:
对于
this.call.bind(this);
部分,this
相当于Array.prototype.push
,那么整体等同于如下:Array.prototype.push.call.bind(Array.prototype.push)
这里的难点在于bind方法,bind的实现比较简单,如下:
想要理解必须化繁为简,理解得越简单,也就理解得越透彻。进一步简化
bind
的原理,等同于谁调用bind
,就返回一个新的function。我们假设函数
fn
调用bind
方法如fn.bind([1, 2])
,经过简化,忽略bind
绑定参数的部分,最终返回如下:以上,将
fn
替换为Array.prototype.push.call
,[1, 2]
替换为Array.prototype.push
,那么:Array.prototype.push.call.bind(Array.prototype.push)
将等同于:这个看起来和反柯里化的第二种实现有些不大相同,不要急,虽然表面上看起来不一致,但骨子里还是一致的。请耐心往下看:
不同的地方在于前半部分
Array.prototype.push.call
,这里它是一个整体,实际上想代表的就是call方法。而我们都知道,所有函数的call方法,最终都是Function.prototype
的call
方法。那么,就有如下恒等式成立:那么以上函数将等同于:
褪去代入的参数,函数可还原为:
综上,最终反柯里化的第三种实现将和第二种实现完全一致,推理完毕,码字不易,喜欢的请点个赞谢谢~
为了加深对
bind
和 柯里化的理解,我还专门撰写了博客深入分析它们。请参看 函数式编程之柯里化与反柯里化 、Function.prototype.bind方法指南 。
喜欢的同学还可以关注我的专栏路易斯前端深度课
基础
call和apply的区别和作用不再赘述
call和apply源码实现
他们很接近,这里只介绍call,举个例子:a.call(b, c)
取出第一个参数x = b || {}
x.fn = a
拼接除第一个参数以外的参数,用逗号分隔,结果为d
创建独立执行环境的函数e = new Function(),函数内部执行x.fn(d)
执行创建的e
方案二的理解
这里就不考虑call和apply扩大对象方法的问题,因为从源码中方法都会动态创建,以下就不再赘述这个问题。
self指向Array.prototype.push
(Function.prototype.call).apply(Array.prototype.push, arguments);
利用刚讲解的源码,把2变形,得出:Array.prototype.push.(Function.prototype.call)(arguments),这里还需要转化,call接受的不是数组,见4。
arguments是类数组对象[arr, 1],把3变形,得出:Array.prototype.push.(Function.prototype.call)(arr, 1)
call的源码已经解释过,于是变化4,得出arr.(Array.prototype.push)(1)
写得好看一点,arr.push(1)