一直以來,都知道 JavaScript 的 object 在 iterate 的時候並不會保證順序,也因此就一直以為是沒有順序的,直到最近仔細看了 MDN 的文件才發現其實是有某種順序性的。
這裡的順序指的是 iterate object 的順序,包含:for…in, for…of, Object.keys(), Object.values()。
Table of Contents
1. for…in 會 iterate 的 properties
要知道 object iterate 的順序,可以從 for…in 是如何 iterate object 來了解(for…of 的 MDN 文件並沒有特別解釋 iterate object 的順序,他請你去參考 for…in 的文件)。
首先看看 for…in 會 iterate 的東西:
- enumerable string properties
- inherited enumerable properties(從原型鏈 – prototype chain 繼承的 property)
不會 iterate 的東西:
- 用 symbols 作為 keys 的 properties
const obj = { hello: 'world', fruit: 'apple' }
obj[Symbol('a')] = 'a'
// 透過原型鏈指定屬性
// 相當於 Object.prototype.objParent = 'parent'
obj.__proto__.objParent = 'parent'
console.log(obj)
// {1: 'test', hello: 'world', Symbol(a): 'a'}
for (const key in obj) {
console.log(key)
}
// 1
// hello
// parent
可以觀察到,如果是用 Symbol 作為 key,那就不會被 iterate
如果透過原型鏈賦予 property,則會被 iterate
2. for…in iterate 的順序(同 for…of)
在 MDN 裡的文件清楚寫到:
Within each component of the prototype chain, all non-negative integer keys (those that can be array indices) will be traversed first in ascending order by value, then other string keys in ascending chronological order of property creation.
因此 iterate 的順序會是:
- 非負整數從小到大的 value
- property 插入的順序
const obj = { hello: 'world', 5: 'apple', 1: 'banana', world: 'hello'}
for (const key in obj) {
console.log(key)
}
// 1
// 5
// hello
// world
可以看到雖然 5 和 1 這兩個 property 插入的順序在 hello 後面,但在 iterate 時,卻是 1 → 5 → hello → world。
會這樣設計的原因其實是:在 JavaScript 中,Array 是透過 Object 繼承而來:
const arr = []
console.log(arr.__proto__.constructor)
// ƒ Array() { [native code] }
console.log(arr.__proto__.__proto__.constructor)
// ƒ Object() { [native code] }
const obj = {}
console.log(arr.__proto__.__proto__ === obj.__proto__)
// true
console.log(typeof(arr))
// object
因此在 iterate array 時,一定是希望 index 從小到大 iterate
const arr = ['apple', 'banana', 'cat']
// array 用 for...in iterate 會取得 index
for (const index in arr) {
console.log(index)
}
// 1
// 2
// 3
3. 結論
用 for…in 或是 for…of iterate object 時,順序會是:非負整數的 key 從小到大,接著才會是按照插入順序排序。
因此如果你希望你的 object 在 iterate 時,會嚴格按照插入排序的話,那你應該要用 Map,避免一些不預期的錯誤。
const map = new Map([
['hello', 'world'],
['5', 'apple'],
['1', 'banana'],
['world', 'hello']]
)
console.log(map)
// Map(4) {'hello' => 'world', '5' => 'apple', '1' => 'banana', 'world' => 'hello'}
for (const [key, value] of map) {
console.log(key)
}
// hello
// 5
// 1
// world
4. Reference
Object.keys() – JavaScript – MDN Web Docs
for…in – JavaScript | MDN – MDN Web Docs
for…of – JavaScript | MDN – MDN Web Docs – Mozilla
如果覺得我的文章有幫助的話,歡迎幫我的粉專按讚哦~謝謝你!