1. 程式人生 > >如何將類陣列轉化為陣列

如何將類陣列轉化為陣列

首先,什麼是類陣列(Array Like)?

一個簡單的定義,如果一個物件有 length 屬性值,則它就是類陣列

那常見的類陣列有哪些呢?

這在 DOM 中甚為常見,如各種元素檢索 API 返回的都是類陣列,如 document.getElementsByTagNamedocument.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)

參考 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]