使用JSON.stringify进行深度比较和克隆是否可行?

22

在尝试多种用于JSON序列化对象的深度比较和复制实现后,我注意到最快的通常是:

function deep_clone(a){
   return JSON.parse(JSON.stringify(a));
};
function is_equal(a,b){
    return JSON.stringify(a) === JSON.stringify(b);
};

我感觉这样是作弊,会有一些问题在未来困扰着我。使用这些是否可以?


2
我只使用可JSON序列化的对象。 - MaiaVictor
1
这篇文章非常严格地说“JSON对象”,并声明“JSON对象”不存在。http://benalman.com/news/2010/03/theres-no-such-thing-as-a-json/ - DanC
@BenjaminGruenbaum 不完全是重复,但那个人正在使用旧的JSON库。 - MaiaVictor
@DanC 那么我应该如何称呼一个可JSON序列化的对象? - MaiaVictor
JSON现在已经在许多浏览器中本地实现,这完全改变了任何性能分析。 - MaiaVictor
1
好的,既然如此就没有问题了。 - Benjamin Gruenbaum
4个回答

32

JavaScript不能保证键的顺序。

如果它们按照同样的顺序输入,这种方法大多数时间会起作用,但它不可靠。

此外,它会针对键以不同顺序输入但内容相等的对象返回 false:

JSON.stringify({ a: 1, b: 2}) === "{"a":1,"b":2}"

JSON.stringify({ b: 2, a: 1}) === "{"b":2,"a":1}"

1
正确。这使得另一个答案是错误的。已经审核了勾选。 - MaiaVictor
9
最近版本的Javascript确实保证了键的顺序。例如可以参考这篇文章:http://2ality.com/2015/10/property-traversal-order-es6.html。实际上,JS引擎早已实现了这个特性。我认为,由于键的顺序现在是明确定义的,因此具有相同键但顺序不同的两个对象不应被视为相等。因此,JSON.stringify比较可行。 - last-child
1
有人尝试过使用排序吗?类似这样的东西:JSON.stringify({ a: 1, b: 2 }).split("").sort().join("") === JSON.stringify({ b: 2, a: 1 }).split("").sort().join("") - wmik
1
@wmik排序不起作用。考虑这个例子JSON.stringify({ a: 12, b: 12 }).split("").sort().join("") === JSON.stringify({ b: 11, a: 22 }).split("").sort().join("")期望的是false,但得到的是true。 - Jobin Mathew

15

我知道这是一个老问题,但我想在答案中再添加一些内容,因为有些人可能会错误地认为只要不用JSON.stringify对成员顺序不固定的对象进行比较/克隆,就可以无问题地使用它进行比较/克隆。 (为了公平起见,接受的答案并不应该让人产生这样的想法; 它说:“如果[成员]按相同顺序输入,则此方法将在大多数情况下有效。”)

代码可能最能说明潜在的问题:

JSON.stringify(NaN) === JSON.stringify(null)
// => true

JSON.stringify(Infinity) === JSON.stringify(null)
// => true

// or, to put it all together:
JSON.stringify({ val1: (1 / 0), val2: parseInt("hi there"), val3: NaN }) === JSON.stringify({ val1: NaN, val2: null, val3: null })
// => true

// and here's the same example with "cloning" rather than comparison:
JSON.parse(JSON.stringify({ val1: (1 / 0), val2: parseInt("hi there"), val3: NaN }))
// => Object {val1: null, val2: null, val3: null}

即使在没有订单问题的情况下(正如其他人所说,这可能是一个问题),这些怪癖也可能会引起麻烦。在大多数情况下,这些怪癖不太可能出现,但了解它们是很好的,因为它们可能导致一些难以找到的错误。


感谢您的添加! - MaiaVictor
4
这个问题涉及到对可转化为JSON格式的对象进行“深度比较和复制”,因此与InfinityNaN以及其他不属于JSON格式的值相比,包含函数、window对象、undefined、正则表达式等对象同样不相关。请注意保留原意,使语言更加通俗易懂。 - last-child
作为一个寻找关于“不一定是JSON可序列化对象”的更一般答案的人,因为这并没有在标题中提到,我非常感谢此回答所提供的补充信息。我认为人们经常忘记了在SO上的答案也可以作为其他面临类似问题的人的参考,更不用说对那些提供额外信息的人持有如此负面的态度实际上对社区是有害的。 - rmobis

0
只要键值对始终按相同顺序排列,就可以使用 stringify 方法并使用深度等于运算符(===)进行比较。

这是严格相等运算符,而不是深度相等运算符,你也可以使用常规相等性(==)来比较两个从JSON.stringify中获取的字符串。 - r g

0
我编写了这个函数来深度比较任何对象数组或值: 如果需要可以使用它 :) 我已经用随机输入顺序的大量对象和数组样本进行了测试。
function c(x, y) {
  if (!x && !y) return !0
  if (!x || !y) return !1
  if (typeof (x) !==
      typeof (y)) return !1
  if (x instanceof Array) {
    if (
      x.length != y.length) return !1
    const f = []
    for (let i = 0; i < x.length; i++) {
      if (!c(x[i], y[i])) f.push(i)
    }
    const g = [...f]
    for (const i of f) {
      let r = !1
      for (const j of g) {
        if (
          c(x[i], y[j])) {
          g.splice(g.indexOf(j), 1)
          r++
          break
        }
      }
      if (!r) { return !1 }
    }
    return !0
  } else if (x instanceof Object) {
    const e1 =
          Object.entries(x)
    try {
      return c(e1, r(Object.entries(y),
        e1))
    } catch (e) {
      return !1
    }
  } else {
    return x === y
  }

  function r(
    u, v) {
    const a = []
    if (u.length != v.length) return u
    for (
      let i = 0; i < v.length; i++) {
      a.push(m(u, v[i][0]))
    }
    return a
  }

  function m(a, k) {
    for (let i = 0; i < a.length; i++) {
      if (a[i][0] === k) return [a[i][0], a[i][1]]
    }
    throw 0
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接