当键名具有数值时,JSON.parse()是否真的对属性进行排序?

时间:2016-10-11 19:19:59

标签: javascript json parsing object

这里有很多关于这个问题的帖子,它们都包含很多可以这样概括的断言:

  1. 永远不会保证以任何方式订购对象属性。
  2. JSON.parse()从不以任何方式对属性进行排序。
  3. 显然我们对上面的#1毫无疑问,所以我们可以合理地预期,对于任何操作,属性仅按照它们出现的顺序进行处理。
    [编辑,遵循@ Bergi的评论:或者至少它们应该以随机顺序出现]

    然后我们可以特别推断#2应该是真的。

    但请看这个片段:
    (顺便说一句:为了显示结果,下面的片段不要使用console.log(),这可能会改变输出的顺序。相反,对象会被for (key in obj)迭代,输出显示在文件)

    
    
    var inputs = [
      '{"c": "C", "a": "A", "b": "B"}',
      '{"3": "C", "1": "A", "2": "B"}',
      '{"c": "C", "a": "A", "3": "C", "1": "A", "b": "B", "2": "B"}'
    ];
    
    for (var i in inputs) {
      var json = inputs[i],
          parsed = JSON.parse(json),
          output = [];
      for (var j in parsed) {
        output.push(j + ': ' + parsed[j]);
      }
      document.write(`JSON: ${json}<br />Parsed: ${output.join(', ')})<hr />`);
    }
    &#13;
    &#13;
    &#13;

    它表明,给定一个具有无序键的JSON字符串:

    • 如果输入的键具有非数字值,则解析后的对象的属性与输入中的属性相同。这与上面的#2假设一致。
    • 相反,当输入的键具有数值时(虽然它们是字符串,因此不会触发解析错误),解析后的对象的属性已排序。这现在与#2假设相矛盾。
    • 更多:当混合数字和非数字键值时,首先显示已排序的数字属性,然后显示其原始中的非数字属性为了即可。

    从那时起,我首先想要得出的结论是,实际上会有(未记录的?)功能,因此JSON.parse()遵循&#34;规则&#34;暴露在上面。

    但是我有了进一步观察的想法,所以下面的代码片段显示了仅仅编码对象的属性是如何排序的:

    &#13;
    &#13;
    var objects = [
      [
        '{"c": "C", "a": "A", "b": "B"}',
        {"c": "C", "a": "A", "b": "B"}
      ],
      [
        '{"3": "C", "1": "A", "2": "B"}',
        {"3": "C", "1": "A", "2": "B"}
      ],
      [
        '{"c": "C", "a": "A", "3": "C", "1": "A", "b": "B", "2": "B"}',
        {"c": "C", "a": "A", "3": "C", "1": "A", "b": "B", "2": "B"}
      ]
    ];
    
    for (var i in objects) {
      var object = objects[i],
          output = [];
      for (var j in object[1]) {
        output.push(j + ': ' + object[1][j]);
      }
      document.write(`Code: ${object[0]}<br />Object: ${output.join(', ')}<hr />`);
    }
    &#13;
    &#13;
    &#13;

    它产生模拟观察,即无论它们编码的顺序如何,属性都按照上述第3条规则存储:

    • 数字命名属性全部放在第一位,排序
    • 接下来设置其他属性,按编码排序

    所以这意味着JSON.parse()没有参与:实际上它似乎是对象构建的一个基本过程。
    这似乎没有记录,至少我能找到。

    有关真实,权威的规则的任何线索吗?

    [编辑,感谢@Oriol&#39的回答]实际上看起来是合成的:

    • 此行为符合ECMA specification rule
    • 此规则应适用于保证特定订单的所有方法,但对于其他情况是可选的。
    • 然而,现代浏览器似乎都选择应用规则,无论采用何种方法,因此存在明显的矛盾。

2 个答案:

答案 0 :(得分:3)

对象的属性没有顺序,因此JSON.parse无法对它们进行排序。但是,当您列出或枚举对象的属性时,顺序可能是明确定义的。

不一定适用于for...in循环,也不一定适用于Object.keys

正如Does ES6 introduce a well-defined order of enumeration for object properties?中详细解释的那样,spec

  

未指定枚举属性的机制和顺序

但对于OrdinaryOwnPropertyKeys

是的

对象具有内部[[OwnPropertyKeys]]方法,例如Object.getOwnPropertyNamesObject.getOwnPropertySymbols

对于普通对象,该方法使用OrdinaryGetOwnProperty抽象操作,该操作以明确定义的顺序返回属性:

  

当调用抽象操作OrdinaryOwnPropertyKeys时   对象 O ,采取以下步骤:

     
      
  1. 成为新的空List
  2.   
  3. 对于作为整数索引的 O 的每个属性键 P ,   升序数字索引顺序      
        
    1. 添加 P 作为的最后一个元素。
    2.   
  4.   
  5. 对于 O 的每个属性键 P ,它是一个String但不是   整数索引,按属性创建的时间顺序递增      
        
    1. 添加 P 作为的最后一个元素。
    2.   
  6.   
  7. 对于作为符号的 O 的每个属性键 P ,按属性创建的按时间顺序递增      
        
    1. 添加 P 作为的最后一个元素。
    2.   
  8.   
  9. 返回
  10.   

因此,由于OrdinaryOwnPropertyKeys需要订单,因此实现可能决定按该顺序在内部存储属性,并在枚举时使用它。这就是你所观察到的,但你不能依赖它。

另请注意,非普通对象(例如代理对象)可能有另一个[[OwnPropertyKeys]]内部方法,因此即使使用Object.getOwnPropertyNames,订单仍可能不同。

答案 1 :(得分:0)

  

所以我们可以合理地期望,对于任何操作,属性仅按照它们出现的顺序进行处理

推理中存在缺陷的地方。鉴于对象属性不能保证有序,我们必须假设任何操作按其认为合适的任何顺序处理属性。

事实上,引擎的发展方式特别是处理整数属性 - 它们就像数组索引一样,并且以比查找表更有效的格式存储。