拷贝是啥?

顾名思义也就是复制,我们知道在JavaScript中基本数据类型都是保存在栈中的,而复杂数据类型(object)是保存在堆中的,在栈中不过是保存了堆的地址(引用)。

浅拷贝与深拷贝

浅深拷贝都是对引用类型的数据而言的,基本数据类型一赋值就开辟了独立的栈空间,互不影响。

  • 浅拷贝:当我们对基本数据类型复制,会把值全部复制过去的。如果是引用类型,也是把值复制过去,不过这个值是地址引用。这样如果其中一个对象改变了这个地址,就会影响到另一个对象。
  • 深拷贝:是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
    1
    2
    3
    4
    5
    6
    7
    8
    //基本数据类型
    let a = 1
    let b = a
    b = 2 //b的值为2,a的值为1互不影响
    //基本数据类型
    let a = [1,2,3]
    let b = a
    b[0] = 2 //b的值为[2,2,3],a的值为[2,2,3]引用了同一个对象

    赋值&&浅拷贝&&深拷贝的区别

    赋值:只是在栈中新建一个变量,指向同一个堆内存,也就是把地址复制过来。
    浅拷贝:会新建个一对象,如果属性是基本类型,则拷贝基本数据类型的值,如果是引用数据类型,则拷贝内存地址。因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
    深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。
    来看看下面的例子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //赋值
    let a = {
    name:'lisi',
    hobby:[1,2,[3,4],5,6]
    }
    let b = a
    b.name = 'zhangsan'
    b.hobby = [3,4,10]
    //a和b的值都为{name:'zhansan',hobby:[3,4,10]}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//浅拷贝
let a = {
name:'lisi',
hobby:[1,2,[3,4],5,6]
}
let b = shallowClone(a)
b.name = 'zhangsan'
b.hobby[1] = 10 // 新旧对象还是共享同一块内存
//a的值为{name:'lisi',hobby:[1,10,[3,4],5,6]}
//b的值为{name:'zhangsan',hobby:[1,10,[3,4],5,6]}
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
1
2
3
4
5
6
7
8
9
10
//深拷贝
let a = {
name:'lisi',
hobby:[1,2,[3,4],5,6]
}
let b = JSON.parse(JSON.stringify(a)) //比较简单地实现深拷贝
b.name = 'zhangsan'
b.hobby[1] = 10 // 新旧对象不是同一块内存
//a的值为{name:'lisi',hobby:[1,2,[3,4],5,6]}
//b的值为{name:'zhangsan',hobby:[1,10,[3,4],5,6]}

浅拷贝实现

Object.assign()

Object.assign()方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

1
2
3
4
5
6
let obj1 = {person:{name:'lisi',age:18},money:666}
let obj2 = Object.assign({},obj1)
obj2.person.age = 20
obj2.money = 777
console.log(obj1) //{person:{name:'lisi',age:20},money:666}
console.log(obj2) //{person:{name:'lisi',age:20},money:777}

注意:当object只有一层的时候,是深拷贝

Array.prototype.concat()

1
2
3
4
5
6
let arr1 = [1,2,{name:'lisi',age:18},3,4]
let arr2 = arr1.concat()
arr2[1] = 10
arr2[2].name = 'zhangsan'
console.log(arr1)//[1,2,{name:'zhangsan',age:18},3,4]
console.log(arr2)//[1,10,{name:'zhangsan',age:18},3,4]

Array.prototype.slice()

1
2
3
4
5
6
let arr1 = [1,2,{name:'lisi',age:18},3,4]
let arr2 = arr1.slice()
arr2[1] = 10
arr2[2].name = 'zhangsan'
console.log(arr1)//[1,2,{name:'zhangsan',age:18},3,4]
console.log(arr2)//[1,10,{name:'zhangsan',age:18},3,4]

展开运算符…

1
2
3
4
5
6
let obj1 = {person:{name:'lisi',age:18},money:666}
let obj2 = {...obj1}
obj2.person.age = 20
obj2.money = 777
console.log(obj1) //{person:{name:'lisi',age:20},money:666}
console.log(obj2) //{person:{name:'lisi',age:20},money:777}

函数库lodash的_.clone方法

1
2
3
4
5
6
let obj1 = {person:{name:'lisi',age:18},money:666}
let obj2 = _.clone(obj1)
obj2.person.age = 20
obj2.money = 777
console.log(obj1) //{person:{name:'lisi',age:20},money:666}
console.log(obj2) //{person:{name:'lisi',age:20},money:777}

深拷贝实现

JSON.parse

1
2
3
4
5
6
7
8
9
10
//浅拷贝
let a = {
name:'lisi',
hobby:[1,2,[3,4],5,6]
}
let b = JSON.parse(JSON.stringify(a)) //比较简单地实现深拷贝
b.name = 'zhangsan'
b.hobby[1] = 10 // 新旧对象不是同一块内存
//a的值为{name:'lisi',hobby:[1,2,[3,4],5,6]}
//b的值为{name:'zhangsan',hobby:[1,10,[3,4],5,6]}

这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,因为这两者基于JSON.stringifyJSON.parse处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了。

jQuery.extend()方法

jquery 有提供一個$.extend可以用来做 Deep Copy;$.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝

1
2
3
let obj1 = {person:{name:'lisi',age:18},money:666}
let obj2 = $.extend(true,{},obj1)
console.log(obj1.person === obj2.person) //false

函数库lodash的_.cloneDeep方法

1
2
3
let obj1 = {person:{name:'lisi',age:18},money:666}
let obj2 = _.cloneDeep(obj1)
console.log(obj1.person === obj2.person) //false

手写深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function deepClone(obj){
if(obj === null) return obj //如果是null返回空
if(typeof obj !== 'object') return obj //如果是基本数据类型或者函数,直接返回
//返回新的日期或者正则对象
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
//新建一个对象,如果是数组就是new Array() 否则是 new Object()
let cloneObj = new obj.constructor();
for(let key in obj){
if(obj.hasOwnProperty(key)){
cloneObj[key] = deepClone(obj[key]); //递归调用
}
}
return cloneObj;
}