简析JavaScript的浅拷贝和深拷贝

227 阅读4分钟

开始之前

首先我们要搞清楚什么是深拷贝和浅拷贝,JavaScript 对象是一种复杂的数据类型,可以包含各种数据类型。

浅拷贝(Shallow Copy):当使用赋值运算符将引用变量复制到新的引用变量中时,会创建引用对象的浅拷贝。简单来说,引用变量主要存储它所引用的对象的地址。当一个新的引用变量被赋予旧引用变量的值时,存储在旧引用变量中的地址被复制到新引用变量中。这意味着新旧引用变量都指向内存中的同一个对象。因此,如果对象的状态通过任何参考变量发生变化,则两者都会反映出来。

深拷贝(Deep Copy):与浅拷贝不同,深拷贝复制旧对象的所有成员,为新对象分配单独的内存位置,然后将复制的成员分配给新对象。这样,两个对象相互独立,并且在对其中任何一个进行任何修改的情况下,另一个不受影响。此外,如果其中一个对象被删除,另一个对象仍保留在内存中。

浅拷贝方法

制作数组或对象的浅拷贝意味着创建对对象内部原始值的新引用,并复制它们。

...扩展符

扩展语法可用于制作对象的浅表副本。这意味着它将复制对象。然而,更深的对象被引用。是一种创建数组或对象的浅拷贝的便捷方法——当没有嵌套时,它工作得很好。

const person = {
    name: 'John',
    age: 21,
}

// 复制对象
const clonePerson = { ... person}

console.log(clonePerson);
//{name: "John", age: 21}

// 改变clonePerson的值
clonePerson.name = 'Peter';

console.log(clonePerson.name);
console.log(person.name);

//Peter
//John

扩展语法可用于制作对象的浅表副本。这意味着它将复制对象。然而,更深的对象被引用。例如,

const person = {
    name: 'John',
    age: 21,

    // the inner objects will change in the shallow copy
    marks: { math: 66, english: 73}
}

// 复制对象
const clonePerson = { ... person}

console.log(clonePerson); // {name: "John", age: 21, marks: {…}}

// 改变clonePerson的值
clonePerson.marks.math = 100;

console.log(clonePerson.marks.math); // 100
console.log(person.marks.math); // 100

这里,当内部对象的值 math 变为 clonePerson 对象的 100 时,person 对象的 math 键的值也发生了变化。

.slice()

特别是对于数组,使用内置的 .slice() 方法与展开运算符的工作方式相同——创建一层的浅拷贝。

.assign()

Object.assign() 方法是 ES6 标准的一部分。 Object.assign() 方法执行复制并从一个或多个对象复制所有属性。可以使用 Object.assign() 创建相同类型的浅拷贝,它可以与任何对象或数组一起使用。

const person = {
    name: 'John',
    age: 21,
}

// 复制对象
const clonePerson = Object.assign({}, person);
//作为第一个参数的空 {} 确保您不会更改原始对象

console.log(clonePerson);

//改变clonePerson的值
clonePerson.name = 'Peter';

console.log(clonePerson.name);
console.log(person.name);

//Peter
//John

Array.from()

复制 JavaScript 数组的另一种方法是使用 Array.from(),它也会进行浅拷贝。 如果一个对象或数组包含其他对象或数组,浅拷贝将不会按照预期工作,因为嵌套对象实际上并未被克隆。 对于深度嵌套的对象,需要一个深拷贝。

深拷贝方法

lodash

lodash 库是 JavaScript 开发人员制作深拷贝的最常见方式。它非常容易使用。请注意,Ramda 的 R.clone() 等效于 lodash 的 _.cloneDeep(),因为 Ramda 没有浅拷贝辅助方法。

Ramda

函数式编程库 Ramda 包括 R.clone() 方法,该方法可以对对象或数组进行深层复制。

自定义递归函数

编写一个递归的 JavaScript 函数来制作嵌套对象或数组的深层副本非常容易。请注意,还需要检查 null,因为 null 的类型是“object”。

const deepCopyFunction = (inObject) => {
  let outObject, value, key

  if (typeof inObject !== "object" || inObject === null) {
    return inObject // 如果不是对象返回这个值
  }

  // 创建一个对象或数组保存值
  outObject = Array.isArray(inObject) ? [] : {}

  for (key in inObject) {
    value = inObject[key]

    // 递归复制嵌套的对象或数组
    outObject[key] = deepCopyFunction(value)
  }

  return outObject
}

JSON.parse/stringify

const person = {
    name: 'John',
    age: 21,
}

// 复制对象
const clonePerson = JSON.parse(JSON.stringify(person));

console.log(clonePerson);
//{name: "John", age: 21}

// 改变clonePerson的值
clonePerson.name = 'Peter';

console.log(clonePerson.name);
console.log(person.name);
//Peter
//John

注意:JSON.parse() 仅适用于 NumberString 对象字面量。它不适用于具有函数或符号属性的对象文字。

rfdc (Really Fast Deep Clone)

const clone = require('rfdc')()
clone({a: 1, b: {c: 2}}) // => {a: 1, b: {c: 2}}