这里的代码是:
Function.prototype.curry = function() {
var slice = Array.prototype.slice,
args = slice.apply(arguments), // no thisArg ? arguments are the sec param [argsArray]
that = this;
return function() {
// thisArg: null
return that.apply(null, args.concat(slice.apply(arguments)));
}
}
以上就是我所理解的。那么,为什么that.apply
有null
参数,而slice.apply
没有呢?
当我把它改为args = slice.apply(null, arguments)
时,它抛出了一个错误,上面写着:
未捕获类型错误:在null或未定义的上调用了Array.prototype.slice
Function.prototype.apply()
哪里错了?
.apply
为函数设置上下文和参数:
my_fn.apply({}, [1,2,3]);
function my_fn() {
console.log(this); // {}
console.log(arguments); // [1,2,3]
}
slice.apply(arguments);
是一种将类似数组的对象转换为实际数组的破解方法,实际上它也可能是.call(arguments);
,因为调用的工作方式几乎与.apply
:类似
my_fn.call({}, 1,2,3); // <- no array but more arguments
function my_fn() {
console.log(this); // {}
console.log(arguments); // [1,2,3]
}
所以that.apply(null, ...
只是没有为函数that
设置上下文。而Array.prototype.slice
希望处理类似数组的对象,如果没有上下文,则会失败。
slice.apply
和that.apply
调用有不同的用途。
简单回顾一下:Function#apply
最多接受两个参数:在调用原始函数期间用作this
的值,以及任何具有要传递给函数的参数(如果有)的类似数组的对象。
slice.apply
调用,比如这个:
args = slice.apply(arguments);
正在传递arguments
作为第一个参数,因此调用slice
时this
引用arguments
对象,并且根本没有参数。这是一个相当常见的习惯用法,用于将类似数组的arguments对象转换为真正的数组。(在ES2015的现代代码中,我们可能会使用args = Array.from(arguments);
。)
that.apply
调用完全是在做其他事情:它调用调用curry
的函数对象,向它传递提供给curry
的参数,然后传递实际调用当前函数时提供的参数。它传递null
作为第一个参数,该值在调用过程中用作this
,这意味着原始函数将被调用,this
引用全局对象(如果处于宽松模式)或null
(处于严格模式)。
不要骑自行车,但如果引用正确,那就不是curry
的好实现:
(您已经在问题中解决了这个问题。)
它创建了两个隐式全局:args
和that
,这是一个非常糟糕的主意。janje认为这可能是《好的部分》中Crockford的curry
的错误引用版本;如果是,则Array.prototype.slice
之后和slice.apply(arguments)
之后的;
应该是,
,而不是Function.prototype.curry = function() { var slice = Array.prototype.slice, // <== comma here args = slice.apply(arguments), // <== comma here that = this; return function() { return that.apply(null, args.concat(slice.apply(arguments))); }; // Crockford probably didn't leave this semicolon out }; // Or this one
它在调用原始函数时阻塞
this
;相反,它应该使用调用当前函数时使用的this
。它正在
Function.prototype
上创建一个可枚举属性;Function.prototype
上的所有其他方法都是不可枚举的,最好保持这种状态。
相反:
(function() {
var slice = Array.prototype.slice;
Object.defineProperty(Function.prototype, "curry", {
value: function() {
var originalFunction = this;
var args = slice.apply(arguments);
return function() {
return originalFunction.apply(this, args.concat(slice.apply(arguments)));
};
},
writable: true,
configurable: true
});
})();
示例:
"use strict";
// Define it
(function() {
var slice = Array.prototype.slice;
Object.defineProperty(Function.prototype, "curry", {
value: function() {
var originalFunction = this;
var args = slice.apply(arguments);
return function() {
return originalFunction.apply(this, args.concat(slice.apply(arguments)));
};
},
writable: true,
configurable: true
});
})();
// Demonstrate it
function foo() {
console.log("this.answer:", this && this.answer);
console.log("args:", arguments);
}
var obj = {answer: 42, foo: foo.curry("curried")};
obj.foo("supplied during call");
可以进行一些优化(不是严格意义上必须在每次调用curried函数时创建一个新数组),但它们并没有带来多大好处。