概念
前提为拷贝类型为引用类型的情况下:
浅拷贝是拷贝一层,属性为对象时,浅拷贝是复制,两个对象指向同一个地址
深拷贝是递归拷贝深层次,属性为对象时,深拷贝是新开栈,两个对象指向不同的地址
浅拷贝的方法:
1. Object.assign()
2. Array.prototype.slice/ Array.prototype.concat()
3. ... 扩展运算符
4. Object.create() // 这个创建的原型对象也是浅拷贝
深拷贝方法:
1. _.cloneDeep()
2. jQuery.extend()
3. JSON.stringify()
缺点:使用JSON.stringify()以及JSON.parse()它是不可以拷贝 undefined ,symbol function, RegExp 等等类型的
4. 手写循环递归 (循环引用要注意,使用weakMap解决)
浅拷贝
浅拷贝的意思就是只复制引用,而未复制真正的值。
const originArray = [1,2,3,4,5];
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneArray = originArray;
const cloneObj = originObj;
console.log(cloneArray); // [1,2,3,4,5]
console.log(originObj); // {a:'a',b:'b',c:Array[3],d:{dd:'dd'}}
cloneArray.push(6);
cloneObj.a = {aa:'aa'};
console.log(cloneArray); // [1,2,3,4,5,6]
console.log(originArray); // [1,2,3,4,5,6]
console.log(cloneObj); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
console.log(originArray); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
上面的代码是最简单的利用 = 赋值操作符实现了一个浅拷贝,可以很清楚的看到,随着 cloneArray 和 cloneObj 改变,originArray 和 originObj 也随着发生了变化。
注意:容易忽略的Object.create()
let parent4 = {
name: "parent4",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
let person4 = Object.create(parent4);
person4.name = "tom";
person4.friends.push("jerry");
let person5 = Object.create(parent4);
person5.friends.push("lucy");
console.log(person4.name); // tom
console.log(person4.name === person4.getName()); // true
console.log(person5.name); // parent4
console.log(person4.friends); // ["p1", "p2", "p3","jerry","lucy"]
console.log(person5.friends); // ["p1", "p2", "p3","jerry","lucy"]
因为Object.create方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,因此修改一个会互相影响。
深拷贝
代码如下:
循环递归实现。
// 使用weakmap解决循环引用
// 优点:
// 1 .WeakMap来记录对象是否被克隆,主要考虑一下三点。
// 2 .WeakMap对象是key=>value形式,不会重复记录
// 3 .WeakMap是弱引用,如果不在使用,空间会直接释放
function deepCopy (obj, hash= new WeakMap()) {
// 不是对象(普通值类型/function),null,undefined,正则,Date都会直接返回
if(obj == null || typeof obj != 'object') {
return obj
}
if(obj instanceof RegExp ) {
return new RegExp(obj)
}
if( obj instanceof Date) {
return new Date(obj)
}
// 判断是否循环引用的(判断属性是不是存在了)
if(hash.get(obj)) return hash.get(obj)
let cloneObj = new obj.constructor()
// 存obj
hash.set(obj, cloneObj)
for(let key in obj) {
// in 循环会遍历原型链的,所以需要判断是否是当前对象的属性
if(obj.hasOwnProperty(key)) {
cloneObj[key] = deepCopy(obj[key], hash)
}
}
return cloneObj
}
其他方法拷贝(都有一些缺点):
Object.assign()
缺点: 当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。
// 接着来看第二种方式 Object.assign(target, source)
var obj1 = {
name: 'zs',
age: 18,
sex: '男',
dog: {
name: '金毛',
age: 2,
yellow: '黄色'
},
friends: ['kele','jiawen'],
say: function () {
console.log(1)
},
obj: {
a: null,
b: undefined,
arr:[1,2,3]
},
c: /a/,
d: null,
e:{}
}
var obj2 = Object.assign({}, obj1);
// 缺点: 当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。
JSON方法
缺点:使用JSON.stringify()以及JSON.parse()它是不可以拷贝 undefined , function,symbol, RegExp 等等类型的
var obj1 = {
name: 'zs',
age: 18,
sex: '男',
dog: {
name: '金毛',
age: 2,
yellow: '黄色'
},
friends: ['kele','jiawen'],
say: function () {
console.log(1)
},
obj: {
a: null,
b: undefined,
arr:[1,2,3]
},
c: /a/,
d: null,
e:{}
}
// 利用JSON方法
// 缺点:使用JSON.stringify()以及JSON.parse()它是不可以拷贝 undefined , function, RegExp 等等类型的
function copy (obj) {
var start = JSON.stringify(obj); // 返回新字符串,不影响原来对象
var result = JSON.parse(start);
return result;
}
var a = copy(obj1);
console.log(a);
a.friends.push('xixi')
console.log(obj1)
// lodash函数库实现深拷贝
// lodash很热门的函数库,提供了 lodash.cloneDeep()实现深拷贝
JQuery实现
var obj1 = {
name: 'zs',
age: 18,
sex: '男',
dog: {
name: '金毛',
age: 2,
yellow: '黄色'
},
friends: ['kele','jiawen'],
say: function () {
console.log(1)
},
obj: {
a: null,
b: undefined,
arr:[1,2,3]
},
c: /a/,
d: null,
e: undefined
}
// true进行深拷贝,{}要拷贝到这来
var newArray = $.extend(true,{},obj1);
// undefined没有拷贝
console.log(newArray)
newArray.dog.name = 'lala'
console.log(obj1)
lodash函数库实现深拷贝
lodash很热门的函数库,提供了 lodash.cloneDeep()实现深拷贝
总结2:如果想更深入去学习。
推荐看这个:
https://github.com/yygmind/blog/issues/29