前言
谈到js的拷贝,其实分为三种:赋值、浅拷贝和深拷贝。赋值是经常用到的操作,但实际上基本数据类型与引用数据类型的存储方式是不同,第一种是将值存在栈内存中的,而另一个只是在栈内存中存了指向堆内存数据的指针。具体不在这里赘述。今天要讨论的深拷贝与浅拷贝。
区别
在说区别之前需要做一个铺垫,我们需要了解几个概念。
1.基本数据类型是在内存中占据固定大小的,保存在栈内存(栈区、stack)中。
2.引用数据类型是保存在堆内存(堆区、heap)中,在栈内存(栈区、stack)中存储只是变量标识符与指向堆中该实体起始地址的指针。
小知识
闭包中的变量并不保存在栈内存中,而是保存在堆内存中。例子如下:1
2
3
4
5
6
7
8
9function A() {
let a = 'right'
function B() {
console.log(a)
}
return B
}
var makeFunc = A()
makeFunc()//right
这里闭包中的变量如果保存在了栈内存中,随着外层中的函数从调用栈中销毁,变量肯定额会被销毁,但是如果是保存在堆内存中,内存函数仍然能访问外层已经销毁函数中的变量。
弄清楚以上这些之后,我们简单来说一下两种拷贝方式的区别。
深拷贝:将B对象拷贝到A对象中,包括B里面的子对象。
浅拷贝:将B对象拷贝到A对象中,但不包括B里面的子对象。
详细来讲,前言中提到的赋值操作,在使用基本类型数据时,会在栈区产生两个独立相互不影响的变量。
但是引用类型,只是改变了引用的指针,但是仍然指向的是堆区的同一个对象,所以相互之间就会产生影响。
而浅拷贝则是重新在堆区创建内存,并且引用类型在第一层时互不影响,但在改变第二层属性时会互相影响。
深拷贝则完全不会受到影响,是完全复制了对象及其属性,是对对象的子对象进行递归拷贝。
例子
1.Object.assign就是一种浅拷贝,只是拷贝了对象的属性的引用。
1 | var obj = { a:{a: "hello", b: 21} } |
对于深层的属性,只是拷贝了栈中的指针。1
2
3
4
5var objA = { a: 10, b: 20, c: 30 }
var objB = Object.assign({}, objA)
objB.b = 100
console.log(objA)//{a: 10, b: 20, c: 30}
console.log(objB)//{a: 10, b: 100, c: 30}
对于第一层则是可以。
2.手动复制
1 | var obj1 = { a: 10, b: 20, c: 30 }; |
这种方式仍然只是可以在一层时可以不相互影响,针对多层时,这种方式依然可以实现,但就显得格外麻烦1
2
3
4
5var obj1 = {a:{a:"right",b:"false"}}
var obj2 = {a:{a:obj.a.a,b:obj.a.b}}
obj2.a.a = "what"
console.log(obj2.a.a)//what
console.log(obj1.a.a)//right
这里如果是var obj3 = {a:obj1.a}
时,则会互相影响,但同样是影响第二层,第一层不会相互影响。
3.JSON.stringfy把对象转换成字符串,再用JSON.parse把字符串转成新的对象。
1 | var obj1 = { body: { a: 10 } }; |
这是一种深拷贝。但是这种方法也有不少坏处,譬如它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。
这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,即那些能够被 json 直接表示的数据结构。RegExp对象是无法通过这种方式深拷贝。
也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON。1
2
3
4
5
6var obj1 = { fun: function(){ console.log(123) } };
var obj2 = JSON.parse(JSON.stringify(obj1));
console.log(typeof obj1.fun);
// 'function'
console.log(typeof obj2.fun);
// 'undefined' <-- 没复制
4.递归拷贝
1 | function deepClone(initalObj, finalObj) { |
5.使用Object.create()方法
这是一种深拷贝1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function 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) ? [] : Object.create(prop);
} else {
obj[i] = prop;
}
}
return obj;
}