JS深浅拷贝

要了解深浅拷贝,需要先弄懂 JS 不同数据类型是如何保存在内存中的

栈内存和堆内存

JS 数据类型分为基本数据类型引用类型,前者保存在栈内存,后者保存在堆内存中。

根本原因在于–

  • 保存在栈内存的必须是大小固定的数据
  • 引用类型的大小不固定,只能保存在堆内存中,但是可以把它的地址写在栈内存中以供我们访问。

如果是基本数据类型,****则按值访问****,操作的就是变量保存的值;如果是引用类型的值,我们只是通过保存在变量中的引用类型的地址来操作实际对象

下面给两张图,就很清楚了

图片

基本数据类型

而对于引用类型的复制–

复制的只是引用地址

1
2
3
4
5
6
var color1 = ["red", "green"];
var color2 = color1; //复制
console.log(color2); //['red','green'];
color1.push("black"); //改变color1的值
console.log(color2); //['red','green','black']
console.log(color2 === color1); //true

可以看到,修改 color1,color2 也变了,这是因为 color1 与 color2 指向堆内存中同一地址的同一对象。二者就是一个东西

图片

引用类型

深拷贝和浅拷贝

  • 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存
    • 浅拷贝方法很简单:
    • 比如直接赋值
    • [].slice()方法:数组的一个浅拷贝
  • 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

图片

弄明白深浅拷贝区别之后,我们再来分析一个“假的深拷贝”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function simpleClone(initalObj) {
var obj = {};
for (var i in initalObj) {
obj[i] = initalObj[i];
}
return obj;
}
let obj1 = {
name: "233",
o: {
a: "1",
},
};
let copy = simpleClone(obj1);
console.log(copy === obj1); // false
obj1.o.a = "我变了";
console.log(copy.o.a); //我变了
  • 以上代码中,虽然创建了一个新的对象 copy,然后将被复制对象 obj1 的各属性挂载到这个 copy 上,此时 copy 和 obj1 确实是两个对象,不共享同一内存空间。

但是–

  • 它们之中的引用类型 o,仍旧指向同一块内存。因此当修改 obj1 的对象属性 o 时,copy 之中的 o 也对应得变了

以上代码是个假的“深拷贝”!,它只使用于对象属性都为基本数据类型时

因此,当复制一个存在引用类型属性的对象时,该方法不适用!

实现深拷贝的几种方法

1.递归调用浅拷贝(深度优先)

  • 第一次调用时,只需要传入要深拷贝的那个对象,此时形参 finalObj 无实参,等同于 var obj = {},该{}即为要返回的那个拷贝对象(它是最外层)
  • 此后的每次递归调用,第 12 行代码都会为那一层的引用类型创建新的[]/{}(就是弄个新内存),以此实现深拷贝
  • prop 用于遍历被拷贝对象的各属性以及其中嵌套的每层引用类型的属性
  • finalObj 用于接收 obj[i],其中记录了被拷贝对象中引用类型的嵌套层次
  • 递归的过程类似于深度优先遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if (prop === obj) {
continue;
}
if (typeof prop === "object") {
//若是引用类型,则为这一层的引用类型新建个[]或{}
obj[i] = prop.constructor === Array ? [] : {}; //再递归
deepClone(prop, obj[i]);
} else {
//若不是引用类型,为基本数据类型,直接以赋值的形式复制(挂载)到obj上
obj[i] = prop;
}
}
return obj;
}

2.借助 JSON 对象

  • JSON.stringify 会将对象变成 JSON 格式的字符串
  • 此时为基本类型,直接复制即可
  • JSON.parse 会将 JSON 格式的字符串转化为JS 对象,会为该对象创建新的内存空间
1
2
3
4
5
function deepClone2(obj) {
var _obj = JSON.stringify(obj),
objClone = JSON.parse(_obj);
return objClone;
}

3.jQuery 的 extend 方法

1
2
var array = [1, 2, 3, 4];
var newArray = $.extend(true, [], array);

lodash 函数库提供的深拷贝方法

lodash 是一个函数库,里面有很多封装好的,功能完善,性能优良的函数,可以直接 require 然后使用

1
2
3
4
5
6
7
8
9
10
// 函数库lodash,有提供_.cloneDeep用来做 Deep Copy。
var _ = require("lodash");
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3],
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false

什么时候使用?

当你希望在多处使用某一个引用类型的值,而不希望其中一处改变会影响到另一处。这时,就可以使用深拷贝

参考:

JS 的深浅拷贝–小凤年 | 简书

JS常见的内存泄漏? JavaScript内存管理_垃圾回收机制

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×