类数组转化为数组

什么是类数组(Array Like)?

一个简单的定义,如果一个对象有 length 属性值,则它就是类数组

常见的类数组有哪些?

这在 DOM 中甚为常见,如各种元素检索 API 返回的都是类数组,如 document.getElementsByTagNamedocument.querySelectorAll 等等。除了 DOM API 中,常见的 function 中的 arguments 也是类数组

将类数组转化为数组

ES6+

ES6 中有现成的 API:Array.from

1
2
// [undefined, undefined, undefined]
Array.from({ length: 3 });

以及运算符 ... 扩展运算符,不过它只能作用于 iterable 对象,即拥有 Symbol(Symbol.iterator) 属性值的对象,意味着可以使用 for of 来循环迭代

1
2
// 适用于 iterable 对象
[...document.querySelectorAll("div")];

但是严格意义上来说,它不能把类数组转化为数组,如 { length: 3 }。它将会抛出异常

1
2
// Uncaught TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))
[...{ length: 3 }];

ES5

我们使用以下数据来代表类数组:

1
2
3
4
5
6
const arrayLike = {
0: 3,
1: 4,
2: 5,
length: 3,
};

ES5 中可以借用 Array API 通过 call/apply 改变 this 或者 arguments 来完成转化。

最常见的转换是 Array.prototype.slice

1
Array.prototype.slice.call(arrayLike);

由于借用 Array API,一切以数组为输入,并以数组为输出的 API 都可以来做数组转换,如

  • Array (借用 arguments)
  • Array.prototype.concat (借用 arguments)
  • Array.prototype.slice (借用 this)
  • Array.prototype.map (借用 this)
  • Array.prototype.filter (借用 this)
1
2
3
4
5
Array.apply(null, arrayLike);
Array.prototype.concat.apply([], arrayLike);
Array.prototype.slice.call(arrayLike);
Array.prototype.map.call(arrayLike, (x) => x);
Array.prototype.filter.call(arrayLike, (x) => 1);

稀疏数组 (sparse array)

使用 Array(n) 将会创建一个稀疏数组,为了节省空间,稀疏数组内含非真实元素,在控制台上将以 empty 显示

1
2
3
4
> [,,,]
[empty × 3]
> Array(3)
[empty × 3]

总结

把类数组转化成数组最靠谱的方式是以下三个

1
2
3
Array.from(arrayLike);
Array.apply(null, arrayLike);
Array.prototype.concat.apply([], arrayLike);

以下几种方式需要考虑稀疏数组的转化

1
2
3
Array.prototype.filter.call(divs, (x) => 1);
Array.prototype.map.call(arrayLike, (x) => x);
Array.prototype.filter.call(arrayLike, (x) => 1);

以下方法要注意是否是 iterable object

1
[...arrayLike];