【问题标题】:Is it fine to use JSON.stringify for deep comparisons and cloning?使用 JSON.stringify 进行深度比较和克隆可以吗?
【发布时间】:2013-03-13 02:52:27
【问题描述】:

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

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

不过,我觉得这是作弊。就像我会发现一些将来会惹恼我的问题。用这些可以吗?

【问题讨论】:

  • 我只使用 JSON 可序列化对象。
  • FWIW 文章严格说“JSON 对象”并声明不存在“JSON 对象”benalman.com/news/2010/03/theres-no-such-thing-as-a-json
  • @BenjaminGruenbaum 并不完全重复,但那家伙无论如何都在使用旧的 JSON 库。
  • @DanC 那么我该如何称呼 JSON 可序列化对象?
  • 很公平,那样的话没有问题。

标签: javascript json equality deep-copy object-comparison


【解决方案1】:

JavaScript 不保证键的顺序。

如果它们以相同的顺序输入,这种方法大部分时间都可以工作,但并不可靠。

此外,对于深度相等但其键输入顺序不同的对象,它会返回 false:

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

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

【讨论】:

  • 是的。这使得另一个答案是错误的。审查了勾号。
  • Javascript 确实保证密钥顺序,在最近的版本中。有关概述,请参见例如 2ality.com/2015/10/property-traversal-order-es6.html。实际上,JS 引擎已经在这样做了。我会争辩说,由于现在定义了键顺序,因此具有不同顺序的相同键的两个对象不应被视为等效。因此JSON.stringify 比较有效。
  • 有没有人尝试过使用排序?类似于JSON.stringify({ a: 1, b: 2 }).split("").sort().join("") === JSON.stringify({ b: 2, a: 1 }).split("").sort().join("")
  • @wmik 排序将不起作用。考虑这个例子 JSON.stringify({ a: 12, b: 12 }).split("").sort().join("") === JSON.stringify({ b: 11, a: 22 }).split("").sort().join("") 预期为假,但结果为真。
【解决方案2】:

我意识到这是一个老问题,但我只是想为答案添加更多内容,因为否则有人可能会离开此页面,错误地认为使用 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}

即使订购不是问题(正如其他人所说,它可能是),这些怪癖也会引起麻烦。在大多数情况下,这些怪癖可能不太可能会抬起他们丑陋的头脑,但最好注意它们,因为它们可能会导致一些非常难以发现的错误。

【讨论】:

  • 感谢您的加入!
  • 该问题询问“深度比较和复制 JSON 可序列化对象”,因此 InfinityNaN 和其他不属于 JSON 的值实际上并不比包含函数、window 对象、undefined、正则表达式等的对象...
  • 作为一个来到这里寻找更一般的“不一定是 JSON 可序列化对象”的答案的人,因为标题中没有提到这一点,我非常感谢这个答案中提供的补充。我认为人们经常忘记,这里关于 SO 的答案也可以作为其他面临类似问题的人的参考,更不用说对提供额外信息的人持这种消极态度实际上对社区有害。
【解决方案3】:

我编写了这个函数来深度比较任何对象数组或值: 如果你愿意,可以使用它:) 我用非常庞大的对象样本测试了它,对象和数组中的条目顺序也是随机的。

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
  }
}

【讨论】:

    【解决方案4】:

    只要键值对始终保持相同的顺序,是的,您可以使用 stringify 使用深度等于运算符 (===) 进行比较。

    【讨论】:

    • 它是严格相等,而不是深度相等运算符,您也可以使用常规相等 (==) 来比较来自 JSON.stringify 的两个字符串
    猜你喜欢
    • 1970-01-01
    • 2017-09-17
    • 1970-01-01
    • 2018-02-28
    • 2011-07-12
    • 1970-01-01
    • 2014-03-29
    • 2017-06-10
    • 2014-05-08
    相关资源
    最近更新 更多