首先,什么是類數組(Array Like)?
一個簡單的定義,如果一個對象有 length 屬性值,則它就是類數組
那常見的類數組有哪些呢?
這在 DOM 中甚為常見,如各種元素檢索 API 返回的都是類數組,如 document.getElementsByTagName,document.querySelectorAll 等等。除了 DOM API 中,常見的 function 中的 arguments 也是類數組
那如何把類數組轉化為數組呢?這是類數組操作時一個典型的場景,也是一個典型的面試題
以下我們將以 { length: 3 } 來指代類數組,來作為演示
節選自 日文 【Q168】在 js 中如何把類數組轉化為數組。另外這里有更多的 前端面試題,歡迎交流
ES6+
ES6 中有現成的 API:Array.from,極為簡單
// [undefined, undefined, undefined]
Array.from({ length: 3 })
除了 Array.from 還有更簡單的運算符 ... 擴展運算符,不過它只能作用於 iterable 對象,即擁有 Symbol(Symbol.iterator) 屬性值
擁有 Symbol(Symbol.iterator) 屬性值,意味着可以使用 for of 來循環迭代
// 適用於 iterable 對象
[...document.querySelectorAll('div')]
但是嚴格意義上來說,它不能把類數組轉化為數組,如 { length: 3 }。它將會拋出異常
// Uncaught TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))
[...{length: 3}]
ES5
在此之前,我們先不使用 { length: 3 },使用以下數據來代表類數組
const arrayLike = {
0: 3,
1: 4,
2: 5,
length: 3
}
在 ES5 中可以借用 Array API 通過 call/apply 改變 this 或者 arguments 來完成轉化。
最常見的轉換是 Array.prototype.slice
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)
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)
此時一切正常,但是忘了一個特例,稀疏數組。在此之前,先做一個題,以下代碼輸出多少
// 該代碼輸出多少
Array(100).map(x => 1)
稀疏數組 (sparse array)
使用 Array(n) 將會創建一個稀疏數組,為了節省空間,稀疏數組內含非真實元素,在控制台上將以 empty 顯示,如下所示
[,,,] 與 Array(3) 都將返回稀疏數組
> [,,,]
[empty × 3]
> Array(3)
[empty × 3]
當類數組為 { length: 3 } 時,一切將類數組做為 this 的方法將都返回稀疏數組,而將類數組做為 arguments 的方法將都返回密集數組
總結
由上總結,把類數組轉化成數組最靠譜的方式是以下三個
Array.from(arrayLike)
Array.apply(null, arrayLike)
Array.prototype.concat.apply([], arrayLike)
以下幾種方法需要考慮稀疏數組的轉化
Array.prototype.filter.call(divs, x => 1)
Array.prototype.map.call(arrayLike, x => x)
Array.prototype.filter.call(arrayLike, x => 1)
以下方法要注意是否是 iterable object
[...arrayLike]
