轻松理解javascript浅拷贝与深拷贝的关系

35 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,点击查看活动详情

一、简述

在编程语言中,拷贝是一项重要的操作。拷贝可以指定义单个对象或集合中的数据,将其副本存储在程序内存其他位置以供访问或使用。从计算机科学角度来看,"拷贝"意味着重新分配内存到另一个变量,以存储值的副本。

二、浅拷贝

浅拷贝(也称为浅复制)是仅复制对象的标识,而不复制它的值。 它使用共享的存储机制,因此,如果复制的值更改,则会影响所有引用的副本。

JavaScript中,复制操作可以发生在基本数据类型(StringNumberBooleannullundefined)以及派生类型之间,如ObjectArrayFunction等等。 对于可变/引用类型,浅复制仅复制一个对象的引用,而不是实际对象本身。

例如:

let a = {name: "Tommy", age: 18};
let b = Object.assign({}, a); // 使用Object.assign()方法浅拷贝 
console.log("a and b:", a, b);

b.age = 40;   // 改变b的age
console.log("a and b:", a, b);

// 输出
// a and b: {name: "Tommy", age: 18} {name: "Tommy", age: 18} 
// a and b: {name: "Tommy", age: 18} {name: "Tommy", age: 40}

注意:可以看到浅拷贝保护了原数据。

let a = { name: 'Tommy', child: { name: 'tommy1' } }
let b = Object.assign({}, a) // 使用Object.assign()方法浅拷贝
console.log('a and b:', a, b)

b.child.name = "tommy2";   // 改变b的child.name值
console.log("a and b:", a, b);

// 输出
// a and b: { name: 'Tommy', child: { name: 'tommy1' } } { name: 'Tommy', child: { name: 'tommy1' } }
// a and b: { name: 'Tommy', child: { name: 'tommy2' } } { name: 'Tommy', child: { name: 'tommy2' } }

注意:

  • 这里可以看到浅拷贝保护了第一层数据,但深层数据应该会受到影响。
  • 除了Object.assign(),当然还能用{...a}三点用法实现。

三、深拷贝

深复制涉及到创建新的内存以容纳复制的数据项,被复制数据项的指针指向新的存储位置而不是原始位置。 一旦深度复制底层数据项,就不会再有引用到原始条目的链接。

虽然深复制比浅复制更昂贵,但提供了更大的灵活性,允许复制的数据独立变化,而不会影响源数据或其他副本。

let a = { name: 'Tommy', child: { name: 'tommy1' } };
let b = JSON.parse(JSON.stringify(a)); // 使用JSON.parse(JSON.stringify())方法实现深拷贝
console.log('a and b:', a, b);

b.child.name = "tommy2";   // 改变b的child.name值
console.log("a and b:", a, b);

// 输出
// a and b: { name: 'Tommy', child: { name: 'tommy1' } } { name: 'Tommy', child: { name: 'tommy1' } }
// a and b: { name: 'Tommy', child: { name: 'tommy1' } } { name: 'Tommy', child: { name: 'tommy2' } }

注意:

  • JSON.stringify已经将对象转为字符串了,所以与原数据没有引用关系,再使用JSON.parse序列化为对象即可。

  • 当然使用JSON.parse(JSON.stringify())转换是有缺点的。

    • 内存消耗。
    • 丢失一些信息,比如函数、正则表达式、undefined等。
    • 对象中含有循环引用,就会报错。
    • 无法处理 Symbols/Maps/Sets 类型的数据。
  • 如果自己不想写深拷贝格式化函数,推荐使用lodash深拷贝插件:_.cloneDeep()实现。

四、总结

总的来说,浅拷贝是极为快速,而深拷贝却是有点慢,但它能够很好地保护拷贝变量免于受到原始变量的破坏。在正确地处理复制策略方面,谨慎就是更好。