速通JS中的浅拷贝与深拷贝

158 阅读5分钟

在我们日常代码编写中,经常会遇到需要复制一个数组或者对象的时候。如果复制对象为原始数据类型,那很简单。但如果复制的对象里包含引用类型时,原对象的引用数据修改时,新复制的对象是否会修改呢?这就是我们今天要探讨的问题——浅拷贝与深拷贝。

我们先通过一些简单的例子来看看它们到底是什么:

let a = 1
let b = a
a = 2

console.log(b);

let obj = {
    c: 3
} 
let newObj = obj
obj.c = 4

console.log(newObj);

image.png

根据这个简单的例子,我们可以知道:

新拷贝的对象不会因为原对象属性的修改而修改的拷贝是深拷贝;反之,新对象会受原对象影响的为浅拷贝(只拷贝了对象的第一层,而里面的子对象拷贝的还是引用地址)。

原始类型一定为深拷贝,没有讨论的意义。我们讨论的浅拷贝与深拷贝都是基于引用类型的拷贝。

浅拷贝

让我们一起看看JS中浅拷贝有那些实现方法

1.Object.create(obj)

Object.create()函数会创建一个新对象,它是一个空对象,因为它没有直接定义任何属性。它的属性是通过原型链继承自 obj

let obj = {
    a: 1,
    b: 2
}

let newObj = Object.create(obj)

obj.a = 3

console.log(newObj.a);
console.log(newObj);

image.png

这个例子中,newObject的a属性会随着被拷贝对象obj的修改而修改,但是newObject并没有复制obj中的属性,而是与obj建立原型链关系。所以Object.create(obj)严格意义上来说并不能算是浅拷贝,只是效果与浅拷贝类似,这里需要注意。

2.Object.assign({},obj2)

const user = {
    name: 'Jack',
    like: {
        n: '泡脚',
        m: '吃鸡'
    }
}

let newObj = Object.assign({},user)

console.log(newObj);

image.png 这就是Object.assign({},...obj)的一个用法,那我们来验证一下,Object.assign({},...obj)是否是浅拷贝呢?

const user = {
    name: 'Jack',
    like: {
        n: '泡脚',
        m: '吃鸡'
    }
}

let newObj = Object.assign({},user)

console.log(newObj);

user.like.n = '喝茶'

console.log(newObj);

image.png 当obj里的引用对象like里的属性发生改变时,拷贝的对象newObject里的引用对象里的值也跟着改变了,意味着它就是浅拷贝。

3.[].concat(arr)

let arr = [{a:1},2]

let newArr = [].concat(arr)

arr[0].a = 10

console.log(newArr);

image.png 同样,原对象的引用类型发生改变时,新拷贝的对象同样发生改变,因此它也是浅拷贝。

4.数组解构

let arr = [1, 2, {a: 3}]
let newArr = [...arr]

arr[0] = 4
arr[2].a = 5
console.log(newArr);

image.png

同样,数组解构也是一种浅拷贝。

5.arr.slice()

const arr = [1, {a: 2}, 3, 4, 5];

const newArr1 = arr.slice(1, 3);  // 左闭右开
arr[1].a = 6
console.log(newArr1);

image.png

6.arr.toReversed().reverse()

  • arr.toReversed()是2022年才引入的新方法,此方法用于返回一个新数组,内容是原数组元素的反向排列。
  • arr.reverse()用于反转一个数组中的元素。要注意的是,该方法会直接修改原数组,而不是创建一个新数组。 那我们先对一个数组进行.toReversed()操作产生一个新的反转过的数组,再用.reverse()就可以拷贝出一个数组了,那我们来验证一下,这是不是浅拷贝呢?
let arr = [1, {a: 2}, 3, 4, 5]

let newArr = arr.toReversed().reverse()
console.log(newArr);

arr[1].a = 6
console.log(newArr);

image.png 显而易见,它同样也是浅拷贝。

那么,关于浅拷贝我们就这六种方法,接下来我们来看看深拷贝是怎么实现的。

深拷贝

1.JSON.parse(JSON.stringify(obj))

定义

  • JSON.stringify(obj) : 这个方法将一个 JavaScript 对象转换为一个 JSON 字符串。它会将对象的所有可枚举属性序列化为字符串,包括嵌套对象,但不包括函数、undefinedSymbol、不属于对象的属性(如原型链),以及 Map 和 Set 等特殊对象。
  • JSON.parse(jsonString) : 这个方法将一个 JSON 字符串解析为一个 JavaScript 对象。它会创建一个新对象,并努力复制字符串中的值。

用法示例

下面是一个简单的示例,演示如何使用 JSON.parse(JSON.stringify(obj)) 来进行深拷贝:

const original = {
    name: 'Alice',
    age: 30,
    hobbies: ['reading', 'traveling'],
    address: {
        city: 'New York',
        zip: '10001'
    }
};

// 使用 JSON.parse 和 JSON.stringify 进行深拷贝
const copy = JSON.parse(JSON.stringify(original));

// 修改拷贝的对象
copy.name = 'Bob';
copy.hobbies.push('cooking');
copy.address.city = 'Los Angeles';

console.log(original.name); // 输出: 'Alice' (原对象未改变)
console.log(original.hobbies); // 输出: ['reading', 'traveling'] (原对象未改变)
console.log(original.address.city); // 输出: 'New York' (原对象未改变)
console.log(copy); // 输出: { name: 'Bob', age: 30, hobbies: ['reading', 'traveling', 'cooking'], address: { city: 'Los Angeles', zip: '10001' } }

原对象的引用类型发生改变也不会改变拷贝对象的引用类型数据,所以JSON.stringify(obj)为深拷贝。

但是使用JSON.stringify(obj)时需要注意一些地方:

  1. 不能识别 bigint
  2. 不能拷贝 undefined Symbol function NaN Infinity
  3. 无法处理循环引用

structuredClone()

由于一直以来js都没有一个完美的深拷贝方法,一直被世界各地程序员诟病,终于,官方顶不住各方压力,再前几年创建了一个直接可以进行深拷贝的方法——structuredClone()

让我们看看用法:

const user = {
    name: 'Jack',
    like: {
        n: '泡脚',
        m: '吃鸡'
    }
}

let newUser = structuredClone(user);

user.like.n = '骑车';

console.log(newUser);

image.png

总结

#拷贝 -只针对引用类型 -基于原对象,拷贝得到一个新对象

1.浅拷贝:新对象会受原对象影响(只拷贝了对象的第一层,而里面的子对象拷贝的还是引用地址)

  • Object.create(x)

  • Object.assign({},obj2)

  • [].concat(arr)

  • 数组解构

  • arr.slice()

  • arr.toReversed().reverse()

  • 实现原理:

2.深拷贝:新对象不受原对象影响

  • JSON.parse(JSON.stringify(obj)) 1.不能识别 bigint 2.不能拷贝 undefined Symbol function NaN Infinity 3.无法处理循环引用

  • structuredClone()

看到这里,相信大家对浅拷贝与深拷贝有了更深的理解。

20200229174423_bzukt.jpg