點燈坊

失くすものさえない今が強くなるチャンスよ

使用 Array.from() 將 Array-like Object 轉成 Array

Sam Xiao's Avatar 2020-01-17

ECMAScript 2015 的 Array.from() 是很有趣的 Method,可將 Array-like Object 與 Iterable Object 轉成真正 Array,因此推導出很多有趣應用。

Version

macOS Mojave 10.14.5
VS Code 1.36.1
Quokka 1.0.236
ECMAScript 2015
Ramda 0.26.1

Array.from()

Array.from()
Array.from(arrayLike[, mapFn[, thisArg]])
將 array-like object 或 iterable 轉成 array

arrayLike:data 為 array-like object 或 iterable

mapFn:optional,可傳入 map function

thisArg:optional,可傳入取代 map function 中 this 的值

回傳值為 array。

有別於一般 array,Array.from() 接受兩種值:

  • Array-like object
  • Iterable

Array-Like Object

let data = {
  0: 'Sam',
  1: 'Kevin',
  2: 'John',
  length: 3
}

let objToArr = obj => {
  let result = []

  for(let i = 0; i < obj.length; i++) {
    result.push(obj[i])
  }

  return result
}

objToArr(data) // ?

Array-like object 與一般 object 不同之處:

  • Property 為 index:012
  • length property

obj 可使用 for loop 與 [],看起來很像 array,所以稱為 array-like object。

from000

let data = {
  0: 'Sam',
  1: 'Kevin',
  2: 'John',
  length: 3
}

let objToArr = obj => Array.from(obj).map(x => x)

objToArr(data) // ?

但因為 obj 只是 array-like object,並不是真正 array,所以沒有 Array.prototype.map() 可用。

但我們可透過 Array.from() 將 array-like object 轉成真正 array,就可使用 Array.prototype.map() 了。

from001

let data = {
  0: 'Sam',
  1: 'Kevin',
  2: 'John',
  length: 3
}

let objToArr = obj => Array.from(obj, x => x)

objToArr(obj) // ?

但先透過 Array.from() 再使用 map() 有個缺點:中間會產生 intermediate array,因此影響效能。

若直接將 map function 傳給 Array.from() 的第二個 argument,結果完全相同,但不需 intermediate array,執行速度更快。

from002

Iterable Object

StringArrayTypedArrayMapSet 都稱為 iterable object,可使用 ES6 的 for...of loop。

String

let data = 'Sam'

let toArr = str => Array.from(str)

toArr(data) // ?

String 為典型 iterable object,因此可透過 Array.from() 轉成 char array。

from003

Set

let data = new Set([1, 2, 3, 1])

let toArr = set => Array.from(set)

toArr(data) // ?

Set 亦為典型 iterable object,亦可使用 Array.from() 轉成 array。

from004

Map

let data = new Map([[1, 2], [3, 4], [5, 6]])

let toArr = map => Array.from(map)

toArr(data) // ?
toArr(data.keys()) // ?
toArr(data.values()) // ?

Map 與其自帶的 keys()values() 亦為典型 iterable object,亦可使用 Array.from() 轉成 array。

from005

Application

由於 Array.from() 的 array-like object 與 map function 特性,因此推導出一些有趣應用。

create()

let create = n => init => Array.from({ length: n }, () => init)

create(3)(1) // ?

若我們想指定特定長度建立 array,且初始值都相同,在 ECMAScript 並沒有內建 function,但我們可以自行建立 create() 完成需求。

因此我們可使用 { length: n } 代表 Array(n),且沒有 sparse array 問題。

from() 的第二個 argument 為 map function,用此建立初始值。

from008

generate()

let generate = n => init => Array.from({ length: n }, (_, i) => i + init)

generate(3)(1); // ?

若我們想指定特定長度建立 array,且給定初始值,隨後會自動建立 sequence,在 ECMAScript 並沒有內建 function,但我們可以自行建立 generate() 完成需求。

因此我們可使用 { length: n } 代表 Array(n),且沒有 sparse array 問題。

from() 的第二個 argument 為 map function,用此建立 sequence。

from009

range()

let range = begin => end => step => 
  Array.from({ length: (end - begin)/step + 1 }, (_, i) => begin + (i * step))

range(1)(10)(2) // ?

Ramda 的 range() 只提供 beginend 兩個 argument,但大部分 library 還提供了第三個 argument:step,我們也可以自行實作之。

當牽涉 step 之後,array 的 length 就不只有 n,而是由 beginendstep 共同決定,且 map function 也包含 step

from010

Conclusion

  • ES6 的 Array.from() 重要性在於將 ECMAScript 兩個很像 array 的 array-like object 與 iterable object 轉成真正 array,如此就可光明正大使用 Array.prototype 下豐富 method,不必再如 ES5 使用 Function.prototype.call() 借用 method
  • Array.from() 另一個亮點是可藉由 Array.from({ length: n }) 建立 length 為 nundefined array,有別於 Array(n) 的 sparse array
  • Array.from(obj, fn) 相當於 Array.from(obj).map(fn),且執行速度更快
  • 由於 Array.from() 可藉由 array-like object 轉成 array,又自帶 map(),很適合建立有初始值 array,因此推導出 create()generate()range() 等有趣應用

Reference

MDN, Array.from()