思考匿名函数的this context到底是什么的同时,找到了相当一部分资料,是教你如何绑定这个this的。他们都不约而同地使用了一个方法:Array.prototype.slice.call(arguments, 1),但是这又引起了另外一个问题,call方法的第一个参数,不是this对象吗?为什么可以传arguments

问题

我们先来看看google出来的资料里面,如何自己实现一个bind方法的代码片段:

1
2
3
4
5
6
7
Function.prototype._bind = function() {
var fn = this;
var context = arguments[0];
var args = Array.prototype.slice.call(arguments, 1); /* <= here */
return fn.apply(context, args.concat([].slice.call(arguments)));
}

这样写的目的很明显,args就是this context以外的所有参数,形式为数组。

之前对call方法浅显的了解,就是它和apply方法一样,都可以方便地绑定this对象,并在接下来的参数中,传入原方法需要的其它参数。

就算是上面这个片段中,最后return的方法,apply方法的第一个对象,其实也是this对象啊。

但是为什么args这里可以直接传arguments进去呢?

MDN-Function.prototype.call()上是这么写的:

fun.call(thisArg[, arg1[, arg2[, …]]])

看到这个写法,我觉得人家官方已经说得很明确,call方法的第一个参数,就一定个this

接着查到,MDN-Arguments object简单带过了一下,说用下面这个方法就可以把本来不是Arrayarguments对象,转化为Array:

var args = Array.prototype.slice.call(arguments);

当时我严重觉得MDN在arguments这里要么就是写错了,要么就是没说清楚具体的原理。

答案

于是我把自己能想到的关键字都组合了一遍,每次google出来的答案,我最少都要翻上三页,直到我认为出来的结果已经没有任何再dig的意义为止。
在折腾了一天以后,我找到了一篇文章,里面说的东西跟如何bind``this一点关系都没有,但是它写出了Array.prototype.slice.call的真正意思:

1
newObject = Array.prototype.slice.call(oldObject, [beginningIndex, [endingIndex]]);

看到这行代码我就豁然开朗了。翻译成中文它是这个意思:

1
新对象 = Array.prototype.slice.call(作用对象, [beginningIndex, [endingIndex]]);

不是什么thisArg,不是什么“call和apply可以让你方便地绑定不同的this对象”,

而是,如果你想要指定某个方法的作用对象,那就使用callapply方法吧(大多数情况下”作用对象”都是this)。

在stackoverflow上,有另外的解释说,这样做其实是强行把slicethis对象设置成了“拥有.length属性,且其它属性都是数字索引的形式”(has a numeric .length property, and a bunch of properties that are numeric indices)的对象,它依然能照常工作。

上面说的这种对象,有个名称叫Array-like object(我自己的翻译是,类数组对象)。有几种典型的对象属于这一类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 一直在讨论的arguments对象 */
function myfunc(){
var args = arguments;
}
/* HTMLCollection */
var uls = document.getElementsByTagName("ul");
/* 形式如下的object */
var obj = {
length: 4,
'0': 'zero',
'1': 'one',
'2': 'two',
'3': 'three'
}

再进一步

看多了下面这种写法:

1
someObject.prototype.someMethod.call()

我就会想,以slice方法威力,为什么不是直接:

1
2
3
[].slice.call(arguments)
/* or */
new Array.slice.call(arguments)

这样呢?使用prototype里面的方法有什么好处么?

答案是:

如果直接用上面这两种方法,会首先创建一个Array对象。然而我们对这个对象并没有兴趣,所以根本不应该创建这么一个多余的对象。从Array的原型链上获取slice方法,是最直接的方式。


参考资料:
[1]. Javascript Tricks: Array.prototype.slice.call(arguments)
[2]. stackoverflow-how does Array.prototype.slice.call() work?
[3]. Converting objects to arrays using Array.prototype.slice.call()