1. 程式人生 > 實用技巧 >為什麼 JS 物件內部屬性遍歷的順序亂了

為什麼 JS 物件內部屬性遍歷的順序亂了

問題重現

需求是要獲取一個車型列表,並且輸出到頁面上按年份排序,故而介面提供的物件簡化如下

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 值為非負整數的屬性,然後根據數字順序對屬性排序首先遍歷出來,然後按照物件定義的順序遍歷餘下的所有屬性。

廣州設計公司https://www.houdianzi.com 我的007辦公資源網站https://www.wode007.com

猜想

按照上面的解釋,那麼我來一個例子

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。