【问题标题】:ES6 React - What are the difference between reference, shallow copy and deep copy and how to compare them?ES6 React - 参考、浅拷贝和深拷贝有什么区别以及如何比较它们?
【发布时间】:2018-03-24 14:16:32
【问题描述】:

我知道这个问题已经讨论过很多次了,我想我有一个基本的想法。我从 StackOverflow 中找到了一些评分最高的答案:

但所有的答案对我来说似乎都很模糊。
让我们考虑下面的例子:

const user = {
  name: "James",
  age: 33,
  highlights: {
    career: "Basketball player",
    NBAChampion: [2012, 2013, 2016],
  },
  promotion: () => ("Get LeBron15 now!"),
};

const james = user;
const clone = { ...user };
const clone2 = Object.assign({}, user);
const clone3 = JSON.parse(JSON.stringify(user));
const clone4 = {
  ...user,
  highlights: {
    ...user.highlights,
    // I comment the it out, so now NBAChampion is a reference rather than copy!
    // NBAChampion: [...user.highlights.NBAChampion]
  }
};

user.age++;
user.highlights.career = "football player";

console.log('james', james, james === user, james == user);
console.log('clone', clone, clone === user, clone == user);
console.log('clone2', clone2, clone2 === user, clone2 == user);
console.log('clone3', clone3, clone3 === user, clone3 == user);
// console.log(clone3.promotion()); // My problem with clone3 is that function's been removed. 
console.log('clone4', clone4, clone4 === user, clone4 == user);
  • james 是一个引用,它总是和用户完全相同;
  • clone 是一个副本。 是浅拷贝还是深拷贝? nameageuser 分开,但 highlights 仍然是参考。
  • clone2 的行为与 clone 完全相同。
  • clone3 是从字符串转换而来的。 它是深拷贝吗?它不是一个完美的克隆,因为函数(如果有的话)不会保存以这种方式转换。
  • clone4 被复制到user 的每一层,所以我可以称之为“深拷贝”。
    但是,如果 spread 运算符有时创建深拷贝,那么 我如何测试新对象是否为深拷贝?

更新:我在clone4 中评论了NBAChampion,所以现在NBAChampion 是参考而不是复制!如果我在user.highlights.NBAChampion 中推送一个新值,clone4 也会更新。
我们应该怎样称呼这种类型的对象?它既不是浅拷贝也不是深拷贝。


为什么这很重要?

React 有一个比较浅拷贝的shouldComponentUpdate() 函数。有link
在 React source code (Line: 356) 中,浅比较是这样完成的:

shouldComponentUpdate(nextProps) {
  return this.props.children !== nextProps.children;
}

在上面的代码演示中,console.log 中的第二个和第三个参数显示了cloneuser 之间的比较结果。但是,只有第一个副本返回 true。 (注意:严格比较和抽象比较没有区别)

如果我将shouldComponentUpdate 应用于上面的演示,显然只有jamesuser 会返回true。所以james 是 Facebook 代码中的浅拷贝。那么我的问题是:

  • 在 JS 中引用和浅拷贝是完全一样的吗?如果不是,为什么 React 这样做?
  • 如何在我的测试用例中测试一个对象是浅拷贝还是深拷贝?

这个问题花了我很多时间来制作。欢迎任何带有示例的清晰解释。
非常感谢。

【问题讨论】:

  • clone 是浅拷贝。 “name”和“age”的原始(和不可变)值是副本,是的,但是对“highlights”对象的引用也是如此;这是参考的副本。
  • "在 JS 中引用和浅拷贝完全一样吗?如果不是,为什么 React 这样做?" - 引用和浅拷贝当然不是一回事。你觉得 React 的肤浅比较到底有什么不直观的地方?此外,詹姆斯不是用户的浅拷贝。而且 React 源代码的链接似乎不正确。

标签: javascript reactjs ecmascript-6


【解决方案1】:

让我们考虑下面的例子

cloneclone2 是浅层的,只有原始对象的属性会受到影响。 clone3clone4 很深。

但是,如果扩展运算符有时只创建深拷贝,那么我如何测试新对象是否是深拷贝?

它在clone4 的情况下创建深层副本 - 只要深度由开发人员控制。通常不需要在 React 中测试一个对象是深拷贝还是只是不同,因为这种检查很昂贵,并且需要遍历两个比较对象中的嵌套对象并逐个属性地比较它们。

性能是 React 依赖不变性的原因。如果新值不等于 ===,则将其视为不同的值。

所以 james 是 Facebook 代码中的浅拷贝。

不是。它是分配给另一个变量的引用。它仍然是同一个对象。

在 JS 中引用和浅拷贝是一回事吗?

参考不是副本。所以它也不是浅拷贝。

我在 clone4 中评论了 NBAChampion,所以现在 NBAChampion 是参考而不是复制!如果我在 user.highlights.NBAChampion 中推送一个新值,clone4 也会更新。 我们应该怎样称呼这种类型的对象?它既不是浅拷贝也不是深拷贝。

这只是一个副本。它没有特定的名称,因为很少需要进行这种选择性复制。如果意图让它像深拷贝一样,那应该被称为错误。

【讨论】:

  • 只要深度由开发人员控制(如 clone4)。 OP 在每个嵌套级别创建了一个浅拷贝,最终得到了深拷贝。
  • 好吧好吧。老实说,我还没有弄清楚 OP 对这些概念的症结所在,因为一旦你“明白”它们并没有那么复杂。我无意贬低,我只是不知道问题的症结在哪里。
【解决方案2】:

对象(或数组)的浅拷贝是一个单独的对象,具有一组匹配的属性名称和属性值。

在进行浅拷贝后,对两个对象(原始对象和副本)逐个属性进行比较将显示所有属性值为===

例如:

let o1 = { a: 1, b: 2, c: { x: "hello", y: "world" } };
let o2 = {};
Object.keys(o1).forEach(propertyName => o2[propertyName] = o1[propertyName]);

现在如果比较o1和o2的属性值,当然是===。特别是,两个对象的属性“c”将引用具有“x”和“y”属性名称的子对象。但是,将o1o2===== 进行比较不会显示相等性,因为无论其内容如何,​​两个不同的对象都不会相互==。 p>

对象的深拷贝是这样一种对象,其中源的每个对象值属性都递归地深拷贝到目标副本中。因为必须为深拷贝目标创建新的对象值,所以这些属性值不会=== 进行比较,因为没有两个不同的对象可以是===

在 JavaScript 这样的语言中进行深度复制可能会出现问题,因为来自属性值的引用“图”可能是循环的,并且因为某些属性值可能是函数。一般来说,深拷贝方法必须逐个应用程序做出一些假设。

根据我的经验,与制作浅拷贝相比,制作对象的深拷贝的需要非常少。

现在,除此之外,比较两个对象的引用,就像在你的 React 代码中一样:

shouldComponentUpdate(nextProps) {
  return this.props.children !== nextProps.children;
}

与浅拷贝或深拷贝无关。这是两个对象属性之间的单一比较,都名为“children”。 this.propsnextProps 引用的对象可能是不同的对象,也可能是同一个对象,其中一个对象可能是另一个对象的浅拷贝或深拷贝,但这对于该比较语句没有影响:所做的只是比较两个特定的“子”属性值是否存在严格的不等式。 (确实,如果nextProps 碰巧this.props 的浅拷贝,反之亦然,那么!== 比较将是错误的,但比较不必知道两个对象的先前历史;这只是两个值的一次比较。)

【讨论】:

  • 如你所说,const clone4 = {...user, highlights: {...user.highlights}}; 将返回false 如果只比较第一层的属性,但它也不是深拷贝。之后怎么样了? jamesclone 都会返回true,那我怎么保证在测试过程中没有副作用呢?
  • @FisNaN 是深拷贝。为什么你会认为不是?这是一件很奇怪的事情,因为您为“亮点”创建了两个单独的值,一个是引用副本,一个是从源浅拷贝的新对象。 edit 如果user 的其他属性是对象引用,我猜它是一个partial 深拷贝。
  • 嵌套的NBAChampion仍然是一个引用,所以当user.highlights.NBAChampion改变时它会改变。这种嵌套的json在实际应用中很常见,使得代码很难测试。
  • @FisNaN 如果您在clone4 内部谈论NBAChampion,那么不,当user.highlights.NBAChampion 更改时它不会更改。这是一个浅拷贝,但由于 NBAChampion 内的所有值都是数字,所以这两个数组之间没有任何联系。
  • 我从来没有觉得需要这样的术语,而且我觉得需要进行深度复制真的很少见;它只是不经常出现(实际上几乎从来没有)。
猜你喜欢
  • 2010-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-07
  • 2012-04-12
  • 2013-06-19
  • 2023-04-05
相关资源
最近更新 更多