ECMAScript 有個很特殊的 Array-like Object,本質是 Object,但用起來很像 Array,但卻又不是 Array,因此有些特性不能使用,必須靠一些特殊方式。
Version
macOS Mojave 10.15.2
VS Code 1.41.1
Quokka 1.0.274
ECMAScript 2015
Definition
Array-like Object
有 index 與length
property 的 object,可使用for
loop 與[]
,用起來很像 array,但無法使用for of
loop 與Array.prototype
下的 method
For Loop
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) // ?
第 1 行
let data = {
0: 'Sam',
1: 'Kevin',
2: 'John',
length: 3
}
obj
為典型 array-like object,以 0
、1
、2
… index 為 property,且還有 length
property。
11 行
for(let i = 0; i < obj.length; i++) {
result.push(obj[i])
}
可使用 []
存取 obj,也能透過 length
使用 for
loop,obj
用起來很像 array,所以稱為 array-like object。
For Of Loop
let data = {
0: 'Sam',
1: 'Kevin',
2: 'John',
length: 3
}
let objToArr = obj => {
let result = [];
for(let x of obj) {
result.push(x);
}
return result
}
objToArr(obj) // ?
普通 array 都可以使用 for of
loop 取代 for
loop,但若對 array-like object 使用 for of
loop 會出現錯誤訊息。
map()
let obj = {
0: 'Sam',
1: 'Kevin',
2: 'John',
length: 3
}
// objToArr :: {a} -> [b]
let objToArr = obj => obj.map(x => `Mr. ${x}`);
objToArr(obj); // ?
普通 array 都可以使用 Array.prototype.map()
,但若對 array-like object 使用 map()
會出現錯誤訊息。
由於 array-like object 無法使用 for of
loop 與 map()
,可以發現 array-like object 與真正 array 還是有些差別。
Application
實務上有 3 處是 array-like object:
- Arguments
- DOM node list
- String
Arguments
function sum() {
let result = 0
for(let i = 0; i < arguments.length; i++) {
result += arguments[i]
}
return result
}
sum(1, 2, 3) // ?
當使用 function declaration 時,可使用 arguments
存取所有 argument,其中 arguments
是 array-like object,可使用 for
loop 存取。
let sum = function() {
let result = 0
for(let i = 0; i < arguments.length; i++) {
result += arguments[i]
}
return result
}
sum(1, 2, 3) // ?
當使用 function expression 時,也可使用 arguments
存取所有 argument,其中 arguments
是 array-like object,可使用 for
loop 存取。
let sum = () => {
let result = 0;
for(let i = 0; i < arguments.length; i++) {
result += arguments[i];
}
return result;
}
sum(1, 2, 3); // ?
當使用 arrow function 時,則無法使用 arguments
。
DOM Node List
let nodes = document.getElementsByTagName('h3')
for(let i = 0; i < nodes.length; i++) {
nodes[i]; // ?
}
由 document.getElementsBy*()
所回傳的都是 array-like object,可使用 for
loop 存取。
String
let name = 'Sam'
for(let i = 0; i < name.length; i++) {
name[i] // ?
}
String 也是 array-like object,可使用 for
loop 存取。
在 ES5 時代,arguments
、DOM node list 與 string 都只是 array-like object,只能使用 for
loop 存取,但 ES6 之後,這三者全都升級成 iterable,因此可使用 for of
loop,但依然無法使用 Array.prototyoe
下的 method,因為 iterable object 仍然不是 array。
為了要讓 array-like object 能使用 Array.prototype
下的 method,有兩種方式:
- 將 array-like object 轉成真正 array
- 借用
Array.prototype
下的 method
Convert to Array
let sum = function() {
return Array.prototype.slice.call(arguments)
.reduce((a, x) => a + x, 0)
}
sum(1, 2, 3) // ?
在 ES5,我們可借用 Array.prototype.slice()
將 array-like object 轉成 array。
call()
來自於 Function.prototype.call()
,其第一個 argument: thisArg
為取代 slice()
中的 this
,傳入 arguments
取代 this
後,相當於 arguments
有了 slice()
。
既然 slice()
傳回真正 array,就可以安全使用 reduce()
了。
let sum = function() {
return [].slice.call(arguments).reduce((a, x) => a + x, 0)
}
sum(1, 2, 3) // ?
也可以使用 []
取代 Array.prototype
,因為 empty array 會繼承 Array.prototype
下所有 method。
let sum = function() {
return Array.from(arguments).reduce((a, x) => a + x, 0)
}
sum(1, 2, 3) // ?
在 ES6 可以使用 Array.from()
直接將 array-like object 轉成 array,語意更清楚。
let sum = function() {
return Array(...arguments).reduce((a, x) => a + x, 0)
}
sum(1, 2, 3) // ?
ES6 的 Array.from()
有個等效寫法,就是透過 spread operator 展開 array-like object,然後透過 Array()
轉成 array。
Generic Method
let sum = function() {
return [].reduce.call(arguments, (a, x) => a + x, 0)
}
sum(1, 2, 3) // ?
既然能借用 slice()
,為什麼不借用 reduce()
呢 ?
直接使用 [].reduce.call()
借用 reduce()
,好似 arguments.reduce()
一般。
Conclusion
- Documents、DOM node list、string 在 ES5 為 array-like object,只能使用
for
loop,但在 ES6 全部升級成 iterable,因此也可使用for of
loop,但仍然不能使用Array.prototype
下的 method - Array-like object 最大的缺點是不能使用
Array.prototype
下的 method,ES5 可藉由slice()
轉成 array,或由 ES6 的Array.from()
轉成 array,或乾脆使用call()
直接借用 generic method
Reference
Dr. Axel Rauschdayer, Array-Like Objects and Generic Methods