在es5标准中,我们经常需要把arguments对象转换成真正的数组

1 // 你可以这样写
2 var arr = Array.prototype.slice.call(arguments)
3
4 // 你还可以这样写
5 var arr = [].slice.call(arguments)
6
7 // 你要是不怕麻烦,你还可以这样写
8 var arr = [].__proto__.slice.call(arguments)

以上三种写法是等价的。

1 // 当你了解原型链,你就知道
2 Array.prototype === [].__proto__  // true
3
4 // [].slice调用的是实例[]的原型对象中的slice方法
5 [].slice === [].__proto__.slice  // true

Array.prototype.slice.call(arguments)原本调用slice的是Array.prototype,而call(arguments)使得调用slice方法的对象改成arguments,你可以想象成

Array.prototype.slice.call(arguments) ~ arguments.slice()

Array.prototype.slice.call(arguments, [begin[, end]]) ~ arguments.slice([begin [, end]])

我们可能会想arguments原型对象是Object.prototype,并没有slice方法,slice方法从哪里来?

这是因为call(arguments)不仅是改变了this的指向,还使得arguments对象继承了Array.prototype中的slice方法。

下面是Array.prototype.slice()源码:指路github地址 587行

function ArraySlice(start, end) {
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.slice");

  var array = TO_OBJECT(this);
  var len = TO_LENGTH(array.length);
  var start_i = TO_INTEGER(start);
  var end_i = len;

  if (!IS_UNDEFINED(end)) end_i = TO_INTEGER(end);

  if (start_i < 0) {
    start_i += len;
    if (start_i < 0) start_i = 0;
  } else {
    if (start_i > len) start_i = len;
  }

  if (end_i < 0) {
    end_i += len;
    if (end_i < 0) end_i = 0;
  } else {
    if (end_i > len) end_i = len;
  }

  var result = ArraySpeciesCreate(array, MaxSimple(end_i - start_i, 0));

  if (end_i < start_i) return result;

  if (UseSparseVariant(array, len, IS_ARRAY(array), end_i - start_i)) {
    %NormalizeElements(array);
    if (IS_ARRAY(result)) %NormalizeElements(result);
    SparseSlice(array, start_i, end_i - start_i, len, result);
  } else {
    SimpleSlice(array, start_i, end_i - start_i, len, result);
  }

  result.length = end_i - start_i;

  return result;
}

arguments可以转换为数组对象也是因为

解析Array.prototype.slice.call(arguments)-LMLPHP

我们可以看到arguments对象成员属性类似数组,且有length属性,那是不是这样类似的对象都可以调用slice呢,我们试验一下

1 var obj = {
2     0: 'foo',
3     1: 'bar',
4     2: 'arg',
5     length: 3
6 }
7 console.log(Array.prototype.slice.call(obj))
8 //  ["foo", "bar", "arg"]

这是可以的!

*PS:es6语法中新增了Array.from(),所以上述类型的对象可以Array.from(obj)就直接转化成数组!

03-06 01:07