Javascript持久排序对象"class"



根据它的规范,JSON元素(和javascript对象)是无序的,所以,即使在几乎所有情况下,当你迭代javascript对象时,你得到的元素与定义的顺序相同;你绝对不能相信这个顺序,因为引擎可以改变它。

这是非常罕见的。我已经能够观察它一次,但我没有找到代码现在,我不记得JS引擎的确切版本(但它是节点)。如果我设法找到它,我会把它添加到这篇文章。

话虽这么说,关键是依赖于这种行为的代码可以(也应该)被认为是有bug的,因为即使它在大多数引擎中按预期工作,它也可能因为内部引擎优化而失败。

例如:

"use strict";
var x = {
    b: 23,
    a: 7
};
function someSorting(input) {
    var output = {};
    Object.keys(input).sort().map(
        function(k){
            output[k] = input[k];
        }
    );
    return output;
};
x = someSorting(x);
// Some smart engine could notice that input and output objects have the
// exact same properties and guess that, attending the unordered nature of
// javascript Object, there is no need to actually execute someSorting();
console.log(x);
// Usually will display: { a: 7, b: 23 }
// But perfectly we could got: { b: 23, a: 7 }

我知道有太多的文献(甚至StackOverflow问题)关于这个(非)问题和"解决方案",通过在单独的数组中排序键来实现预期行为。

但是与简单地信任键顺序相比,这样做的代码太混乱了。

我很确定这可以以一种更优雅的方式实现,通过实现所谓的"sObject"替代方案,将本机Object作为其原型,但重载其本机迭代器和setter,以便:

  • 当添加任何新属性时,它的键被附加到底层维护的数组索引中。
  • 当迭代sObject 实例时,自定义迭代器使用该索引按正确顺序检索元素。

总而言之:实际对象规范是正确的,因为在大多数情况下,属性顺序并不关心。所以我认为引擎优化可以搞砸它是美妙的。

但是如果有另一个sObject也很棒,我们可以这样做:

var x = new sObject({b: 23, a: 7});

…并且相信我们可以以完全相同的顺序迭代它,或者,也/至少,对它执行一些排序任务,并相信这不会被改变。

当然! !我用原生javascript对象初始化它,所以,事实上,理论上我们不能相信它会被正确填充(甚至我无法想象为什么任何引擎优化应该在任何操作之前改变它)。

我使用这种符号是为了简洁(而且,我承认),因为我希望,在这种情况下,应该总是有效(即使我不是很确定)。然而,我们甚至可以稍后对它进行排序(在大多数情况下,我们会这样做)或使用其他类型的初始化,如提供一个JSON字符串或具有单个键和值对的对象数组(或数组)。

我担心的是:这样的事情还存在吗?我没能找到。但我肯定不是第一个这么想的人……

我可以试着实现它(我正在考虑)。我认为这是可能的,而且我可以做到。但这并没有那么简单,所以首先我想确保我没有重新发明轮子…

所以任何评论,建议等…欢迎。

当然,您可以这样做。您将需要一些机器,如Object.observer,目前仅在Chrome中可用。我们可以这样定义:

function myObject(object) {
  // if the object already has keys, bring them in in whatever order.
  var keys = Object.keys(object);
  // Override Object.keys to return our list of keys.
  Object.defineProperty(object, 'keys', { get: function() { return keys; });
  // Watch the object for new or deleted properties.
  // Add new ones at the end, to preserve order.
  Object.observe(object, function(changes) {
    changes.forEach(function(change) {
      if (change.type === 'add') keys.push(change.name);
      if (change.type === 'delete') keys = keys.filter(function(key) {
        return key === change.name;
      });
    });
  });
  return object;
}

请注意,Object.observe是异步的,所以即使在您向对象添加属性之后,它也不会反映在自定义的keys属性中,直到时钟滴答之后,尽管理论上您可以使用Object.deliverChangedRecords

上面的方法使用一个函数,该函数将新的有序键功能添加到现有对象中。当然还有其他的设计方法。

这个"解决方案"显然不能控制for...in循环的行为。

最新更新