JS中的浅拷贝和深拷贝

JS 内存管理

JS 内存管理,往深了挖很复杂,这里只做简单的介绍,帮助理解 js 的基本类型和引用类型,为了后面讲解深度克隆做铺垫,我们知道 JS 拥有自动的垃圾回收机制,这样就使得很多前端开发人员不是很重视内存管理这一块。但是其实这一部分的内容对于理解 JS 中原型与原型链,闭包,递归都是非常有帮助的。

在 JS 中,每一个数据都需要一个内存空间。内存空间又被分为两种:栈(stack)、堆(heap)

基础数据类型和栈内存

JS 中的基础数据类型,我们也称之为原始数据类型,这些值都有固定的大小,往往都保存在栈内存中,由系统自动分配存储空间。我们可以直接操作保存在栈内存空间的值,因此基础数据类型都是按值访问。也就是说,它们的值直接存储在变量访问的位置。

引用数据类型与堆内存

与 java 等其他语言不同,JS 的引用数据类型,比如数组 Array,它们值的大小是不固定的,可以再不声明长度的情况下,动态填充。引用数据类型的值是保存在堆内存中的对象。

JavaScript 不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。

在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。

这里的引用,我们可以粗浅地理解为保存在栈内存中的一个地址,该地址与堆内存的实际值相关联。

为了更好的搞懂栈内存与堆内存,我们可以结合以下例子进行理解。

var a1 = 0; // 栈
var a2 = 'this is string'; // 栈
var a3 = null; // 栈

var b = { m: 20 }; // 变量b存在于栈中,{m: 20} 作为对象存在于堆内存中
var c = [1, 2, 3]; // 变量c存在于栈中,[1, 2, 3] 作为对象存在于堆内存中

JS 数据类型的特点

在栈内存中的数据发生复制行为时,系统会自动为新的变量分配一个新的内存空间。

let a = 20;
let b = a;
b = 30;
console.log(a); // 这时a的值是多少?

在栈内存中的数据发生复制行为时,系统会自动为新的变量分配一个新的内存空间。上例中 let b = a 执行之后,a 与 b 虽然值都等于 20,但是他们其实已经是相互独立互不影响的值了。

let m = { a: 10, b: 20 };
let n = m;
n.a = 15;
console.log(m.a); // 这时m.a的值是多少

我们通过 let n = m 执行一次复制引用类型的操作。引用类型的复制同样也会为新的变量自动分配一个新的值保存在栈内存中,但不同的是,这个新的值,仅仅只是引用类型存在栈内存中的一个地址指针。当地址指针相同时,尽管他们相互独立,但是在堆内存中访问到的具体对象实际上是同一个。

因此当我改变 n 时,m 也发生了变化。此时输出的 m.a 的值也变成了 15,这就是引用类型的特性。

什么是浅拷贝和深拷贝?

深浅是针对引用类型的,为描述简单,下文称为“对象”。

浅拷贝:只拷贝了引用,新旧两个对象的值是堆里面的同一个值,因此修改会互相影响

let A = { a: 1, b: 2 };
// 为方便描述,下文我们称 { a: 1, b: 2 } 为“堆对象1”。

let B = A;
// 赋值是浅拷贝,不会拷贝堆对象1

B === A;
// true,A和B的值相同,都是堆对象1

B.a = 10;
// 修改了B的a属性,其实就是修改堆对象1的a属性

console.log(A);
// { a: 10, b: 2 },堆对象1在上一步被修改了

B = {};
// 创建了一个空对象赋值给B,B的值被覆盖为新的堆对象(B不再指向堆对象1)

console.log(A);
// { a: 10, b: 2 },A没有变化

B === A;
// false,B不再指向堆对象1

深拷贝:既拷贝了引用,又拷贝了对象存储在堆里面的值。修改不会互相影响

let A = { a: 1, b: 2 };

let B = JSON.parse(JSON.stringify(A));
// 使用JSON进行深拷贝(但无法拷贝函数)

B === A;
// false,虽然值都是 { a: 1, b: 2 },却不是同一个堆对象,此处是区分深浅拷贝的关键点

B.a = 10;
// 修改了B的a属性

console.log(B);
// { a: 10, b: 2 }

console.log(A);
// { a: 1, b: 2 },A不受B影响

总结

浅拷贝,不会拷贝堆;

浅拷贝方法:

// 数组
[].concat
[].slice

// 对象
Object.assign()

深拷贝,会拷贝堆,子对象递归拷贝。

深拷贝方法:

JSON.parse(JSON.stringify(obj));

_.cloneDeep(obj);

参考 https://www.jianshu.com/p/2a3728cded4c

© 2022  Arvin Xiang
Built with ❤️ by myself