問題重現
需求是要獲取一個車型列表,並且輸出到頁面上按年份排序,故而接口提供的對象簡化如下
let obj = {
'2018': {
modelCode: "204313",
modelName: "2018款 Vanquish 6.0L S Coupe"
},
'2017': {
modelCode: "202479",
modelName: "2017款 Rapide 6.0L AMR"
},
'2013': {
modelCode: "139705",
modelName: "2013款 Rapide 6.0L S"
}
}
console.log(obj)
// {2013: {…}, 2017: {…}, 2018: {…}}
??? 為什么 2013 在前面了,用戶肯定希望先看到新的車型的,這不科學!
解釋
查閱了 ECMA-262 3rd edition ,如下文 It is an unordered collection of properties
就說到 ES3 標准的對象不排序,插入是啥順序,遍歷就是啥順序
An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive value, object, or function. A function stored in a property of an object is called a method.
而我查閱了 ECMA-262 5.1 edition,如下文,讀者們應該留意到了少了 unordered collection
的描述。並且之后的 ES 版本對對象的描述都是如此。
an object is a member of the remaining built-in type Object; and a function is a callable object. A function that is associated with an object via a property is a method.
故此,我能得出結論 Chrome 等新版瀏覽器 JS 引擎遵循的是新版 ECMA-262 5th。因此,使用 for-in 語句遍歷對象屬性時遍歷書序並非屬性構建順序。而 IE6、7、8 等舊版本瀏覽器的 JS 解析引擎遵循的是較老的 ECMA-262 3rd,屬性遍歷順序由屬性構建的順序決定。
故此 Chrome 的 JS 引擎遍歷對象屬性時會遵循一個規律:
本文的原文:它們會先提取所有 key 的 parseFloat 值為非負整數的屬性,然后根據數字順序對屬性排序首先遍歷出來,然后按照對象定義的順序遍歷余下的所有屬性。
上一句更正:它們會先提取所有 key 能被轉為數字(並不是 parseFloat,是內部的方法),值為非負整數的屬性,然后根據數字順序對屬性排序首先遍歷出來,然后按照對象定義的順序遍歷余下的所有屬性。
猜想
按照上面的解釋,那么我來一個例子
let obj = {
'b': 'testb',
'a': 'testa',
'1': 'test1',
'測': 'test測',
'2': 'test2'
}
console.log(Object.keys(obj));
// [1, 2, 'b', 'a', '測']
果然會把 '1' 和 '2' 這種能被 parseFloat 轉化為正整數的提到前面並且按照升序排
而 'a' 和 '測' 沒法轉為整數那就排在 '1'、'2' 后並按照構建時的順序拍
解決問題
回到問題,對象既然不能保證其順序,那么使用數組來進行遍歷吧。
當然業務中如果需要查某個年份的車型,而不想要每次都遍歷一遍的來找的話。可以維護兩份數據。一份數組,用於遍歷輸出,一份對象,用於查。
補充
直到最近在極客時間翻閱到李兵老師的圖解 Google V8 的第三節 "V8采用了哪些策略提升了對象屬性的訪問速度?" 時,我終於發現了更深層次的解釋。
借下圖說法:V8 里的對象其實維護兩個屬性,會把數字放入線性的 elements 屬性中,並按照順序存放。會把非數字的屬性放入 properties 中,不會排序,順便說一句它可能是線性結構,取決於屬性數量的多少。遍歷屬性時先 elements 而后在 properties。