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:
0
、1
、2
… - 有
length
property
obj
可使用 for
loop 與 []
,看起來很像 array,所以稱為 array-like object。
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()
了。
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,執行速度更快。
Iterable Object
String
、Array
、TypedArray
、Map
與 Set
都稱為 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。
Set
let data = new Set([1, 2, 3, 1])
let toArr = set => Array.from(set)
toArr(data) // ?
Set 亦為典型 iterable object,亦可使用 Array.from()
轉成 array。
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。
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,用此建立初始值。
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。
range()
let range = begin => end => step =>
Array.from({ length: (end - begin)/step + 1 }, (_, i) => begin + (i * step))
range(1)(10)(2) // ?
Ramda 的 range()
只提供 begin
與 end
兩個 argument,但大部分 library 還提供了第三個 argument:step
,我們也可以自行實作之。
當牽涉 step
之後,array 的 length 就不只有 n
,而是由 begin
、end
與 step
共同決定,且 map function 也包含 step
。
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 為n
的undefined
array,有別於Array(n)
的 sparse arrayArray.from(obj, fn)
相當於Array.from(obj).map(fn)
,且執行速度更快- 由於
Array.from()
可藉由 array-like object 轉成 array,又自帶map()
,很適合建立有初始值 array,因此推導出create()
、generate()
與range()
等有趣應用