ES6 React - 引用、浅拷贝和深拷贝之间有什么区别以及如何比较它们?

IT技术 javascript reactjs ecmascript-6
2021-05-01 12:53:30

我知道这个问题已经讨论过很多次了,我想我已经有了一个基本的想法。我从 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是一个副本。是浅拷贝还是深拷贝?nameage从分离user,但highlights仍然是一个参考。
  • clone2行为与 完全相同clone
  • clone3从字符串转换而来。是深拷贝吗?它不是一个完美的克隆,因为函数(如果有的话)不能以这种方式进行转换。
  • clone4的每一层都被复制user,所以我可以称之为“深度复制”。
    但是,如果扩展运算符有时只创建深拷贝,那么我如何测试新对象是否为深拷贝?

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


为什么这很重要?

React有一个shouldComponentUpdate()比较浅拷贝功能。链接
在 React源代码 (Line: 356) 中浅比较是这样完成的:

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

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

如果我申请shouldComponentUpdate上面的演示,显然只有james并且user会返回true. jamesFacebook 代码中的浅拷贝也是如此那么我的问题是:

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

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

2个回答

让我们考虑下面的例子

clone并且clone2很浅,只影响原始对象的属性。clone3并且clone4很深。

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

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

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

所以 James 是 Facebook 代码中的一个浅拷贝。

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

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

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

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

这只是一个副本。它没有特定的名称,因为很少需要进行这样的选择性复制。如果意图使其表现得像深拷贝,则应称为错误。

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

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

例如:

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与其中之一==或或===不会显示相等,因为==无论其内容如何,两个不同的对象永远不会相互关联。

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

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

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

现在,除此之外,在您的 React 代码中比较两个对象引用

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

与浅拷贝或深拷贝无关。这是两个对象属性之间的单一比较,都命名为“子级”。this.props引用的对象nextProps可能是不同的对象,也可能是同一个对象,一个可能是另一个的浅拷贝或深拷贝,但这对比较语句没有区别:所做的只是比较两个特定的“孩子” " 严格不平等的财产value。(确实,如果nextProps 碰巧是 的浅拷贝this.props,反之亦然,则!==比较将是错误的,但比较不必知道两个对象的先前历史;它只是两个值的单个比较.)