Skip to content

关于深拷贝

在做项目时遇到了表单填写后,不点击提交或确定,其数据也发生了变化

这时候,我们需要使用到深拷贝去解决这个问题:

深拷贝:创建一个新的对象和数组,将原对象的各项属性的"值"(数组的所有元素)拷贝过来,是"值"而不是"引用",新对象跟原对象不共享内存,修改新对象不会影响到原对象。

浅拷贝(Shallow Copy):将原对象的引用复制给新对象,新对象与原对象共享同一个内存地址,修改新对象会影响原对象,反之亦然。浅拷贝只复制了对象的引用,而没有复制对象本身。

解决方法一

将数据库获取数据赋值到一个tempIndex中(非表单中的数据),当点击按钮修改非表单数据时深拷贝tempIndex,赋值到index,在表单中对index做修改,点击确定或提交,把数据发送至后端,这样在没有点击确定或提交时原数据才不会跟着改变。

缺点

仅能用于防止意外修改,未实现深拷贝

解决方法二

javascript
function cloneJSON(source) {
    return JSON.parse(JSON.stringify(source));
}

通过将对象转换为 JSON 字符串,然后再将 JSON 字符串解析为新对象的方式实现深拷贝,适用于大多数简单的对象和数组

  1. 使用 JSON.stringify(source) 将源对象(source)序列化为 JSON 字符串。这个过程会将对象的所有属性和值转换为字符串表示。

  2. 使用 JSON.parse() 将 JSON 字符串解析为新的对象。这个过程会创建一个新的对象,其中包含源对象的所有属性和值的拷贝。

缺点

  • 无法处理循环引用的对象。如果对象中存在循环引用,JSON.stringify() 会抛出错误。
  • 无法拷贝函数、正则表达式、undefinedfunctionsymbol等特殊对象。这些对象在序列化和解析过程中会丢失。
  • 无法保留对象的原型链。新对象的原型链会被重置为 Object.prototype

解决方法三:递归实现

javascript
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  let clone = Array.isArray(obj) ? [] : {};

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key]);
    }
  }

  return clone;
}
  1. 在循环中,函数使用 hasOwnProperty() 方法来检查属性是否为 obj 自身的属性,以避免拷贝原型链上的属性。

  2. 对于每个属性,函数递归调用 deepClone() 函数来拷贝属性的值,并将拷贝后的值赋给 clone 对应的属性。

能够处理复杂的对象结构,包括嵌套的对象和数组。它会递归地遍历对象的所有属性,并对每个属性进行深拷贝。这样可以确保拷贝后的对象与原始对象完全独立,互不影响。

缺点

  • 仍然无法处理循环引用的对象,因为递归调用会导致无限循环。

  • 无法拷贝函数、正则表达式、undefinedfunctionsymbol等特殊对象。

解决方法四:使用第三方库

lodash 是一个流行的 JavaScript 工具库,提供了许多实用的功能,包括深拷贝。您可以使用 lodash 的 cloneDeep 函数实现深拷贝:

javascript
const _ = require('lodash');

const newObj = _.cloneDeep(oldObj);

问题

Q1:如果我只需要对数组或对象一层进行拷贝,是不是可以用扩展运算符,为什么扩展运算符会拷贝对数组或对象的第一层,而不是所有层?

A:扩展运算符 ... 只会拷贝对象的第一层属性,而不会拷贝嵌套对象。这意味着,如果对象中包含嵌套对象,使用扩展运算符只会拷贝嵌套对象的引用,而不会拷贝嵌套对象本身。 例如:

javascript
const original = {
  a: 1,
  b: {
    c: 2,
    d: 3
  }
};

const shallowCopy = { ...original };
shallowCopy.b.c = 42;

console.log(original.b.c); // 输出 42

shallowCopy.a=5
console.log(original.a); // 输出 1

在这个例子中,original 对象包含一个嵌套对象 b。使用扩展运算符 ...original 进行浅拷贝,得到 shallowCopy。当修改 shallowCopy.b.c 的值时,original.b.c 的值也会发生变化,因为 shallowCopy.boriginal.b 指向同一个对象。

缺点

  • 使用扩展运算符可以实现一维的深拷贝,对于嵌套对象或者多层数组,扩展运算符只会拷贝第一层,内部的嵌套对象仍然是引用。
  • 无法拷贝对象的不可枚举属性。使用扩展运算符只能拷贝对象的可枚举属性,不可枚举属性会被忽略。
  • 无法拷贝对象的原型。使用扩展运算符只能拷贝对象自身的属性,原型链上的属性会被忽略。

Released under the MIT License.