深,浅拷贝

深拷贝,浅拷贝的概念

简单点来说,就是假设通过B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,如果B没变,那就是深拷贝。

为什么拷贝有深浅之分

我们来举个浅拷贝例子:

1
2
3
4
5
let a=[0,1,2,3,4],
b=a;
console.log(a===b);
a[0]=1;
console.log(a,b);

结果如下:

b复制给了a,为啥修改数组a,数组b也跟着变了。

那么这里,就得引入基本数据类型与引用数据类型的概念了

基本数据类型有,number,string,boolean,null,undefined,symbol以及未来ES10新增的BigInt(任意精度整数)七类。

引用数据类型(Object类)有常规名值对的无序对象{a:1},数组[1,2,3],以及函数等。

而这两类数据存储分别是这样的:

a.基本类型–名值存储在栈内存中,例如let a=1;

当你b=a复制时,栈内存会新开辟一个内存,例如这样:

所以当你此时修改a=2,对b并不会造成影响,因为此时的b已自食其力,翅膀硬了,不受a的影响了。当然,let a=1,b=a;虽然b不受a影响,但这也算不上深拷贝,因为深拷贝本身只针对较为复杂的object类型数据。

b.引用数据类型–名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值,我们以上面浅拷贝的例子画个图:

当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。

而当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。

那,要是在堆内存中也开辟一个新的内存专门为b存放值,就像基本类型那样,岂不就达到深拷贝的效果了

实现深拷贝的方法

利用 递归 来实现深复制,对属性中所有引用类型的值,遍历到是基本类型的值为止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function deepClone(source){    
if(!source && typeof source !== 'object'){
throw new Error('error arguments', 'shallowClone');
}
var targetObj = Array.isArray(source) ? [] : {};
for(var keys in source){
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){
targetObj[keys] = deepClone(source[keys]); //递归
}else{
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}

检测一下

1
2
3
4
5
6
var a = {name:"jack",age:20};
var b = deepClone(a);
console.log(a === b);
a.age = 30;
console.log(a);
console.log(b);

jQuery中的 extend(true, target, object1 [, objectN ])

  • $.extend( [deep ], target, object1 [, objectN ] )
    deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝

    target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。

    object1 objectN可选。 Object类型 第一个以及第N个被合并的对象。

深拷贝:

1
2
3
4
5
6
7
8
var obj = {name:'xixi',age:20,company : { name : '腾讯', address : '深圳'} };
var obj_extend = $.extend(true,{}, obj);
//extend方法,第一个参数为true,为深拷贝,为false,或者没有为浅拷贝。
console.log(obj === obj_extend);
obj.company.name = "ali";
obj.name = "hei";
console.log(obj);
console.log(obj_extend);

浅拷贝

1
2
3
4
5
6
var obj = {name:"xixi",age:20};
var obj_extend = $.extend(false,{}, obj); //extend方法,第一个参数为true,为深拷贝,为false,或者没有为浅拷贝。
console.log(obj === obj_extend);
obj.name = "heihei";
console.log(obj);
console.log(obj_extend);

通过引入js的实用库 Lodash

例子:

1
2
3
4
5
6
var objects = [{ 'a': 1 }, { 'b': 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

实现浅拷贝的方法

JSON.parse(JSON.stringify())

1
2
3
4
5
6
7
8
9
10
11
12
var syb = Symbol('obj');
var person = {
name :'tino',
say: function(){
console.log('hi');
},
ok: syb,
un: undefined
}
var copy = JSON.parse(JSON.stringify(person))
// copy
// {name: "tino"}

ps: 当值为undefined、function、symbol 会在转换过程中被忽略。。。所以,对象值有这三种的话用这种方法会导致属性丢失。

Array 的 slice 和 concat 方法

1
2
3
4
5
6
var a = [[1,2,3],4,5];
var b = a.slice();
console.log(a === b);
a[0][0] = 6;
console.log(a);
console.log(b);

Object.assgin()

1
2
3
4
5
6
7
8
9
10
11
12
var person1 = {
name: "xiaoming",
age: 18,
job: "programmer"
}
var person2 = {
age: 21,
stature: "181cm"
}
var person = Object.assign(person1,person2);

console.log(person)

其实总结一下就是:

Array 的 slice 和 concat 和 Object.assign(),他们都会复制第一层的值,对于 第一层 的值都是 深拷贝,而到 第二层 如果key是引用类型的时候 就是 浅拷贝 。

总结

如果要复制的对象或者数组都是简单数据类型,放心大胆的使用。如果存在深层次的引用,选择方法的时候要慎重。