# 1.深拷贝VS浅拷贝
深拷贝和浅拷贝都是针对的引用类型,JS中的变量类型分为值类型(基本类型)和引用类型;对值类型进行复制操作会对值进行一份拷贝, 而对引用类型赋值,则会进行地址的拷贝,最终两个变量指向同一份数据。
// 基本类型
var a = 1;
var b = a;
a = 2;
console.log(a, b); // 2, 1 ,a b指向不同的数据
// 引用类型指向同一份数据
var a = {c: 1};
var b = a;
a.c = 2;
console.log(a.c, b.c); // 2, 2 全是2,a b指向同一份数据
对于引用来说,a和b 指向同一个堆内存中的对象,因为本质上只是栈内存中地址得复制。。当任意修改其中一个的时候,另一个也会被改变。这样在某些时刻可能会给我们带来一些困扰, 更多的时候,我们不希望联动效果的出现。
根据拷贝的层级不同可以分为浅拷贝和深拷贝:
浅拷贝就是只进行一层拷贝
深拷贝就是无限层级拷贝(拷贝到底)
# 2.浅拷贝实现
方式一:
/**
hasOwnProperty 对象是否包含自身属性,非继承属性
**/
function shallowclone(src) {
let target = {};
for(let i in src) {
if(src.hasOwnProperty(i)){
target[i] = src[i];
}
}
return target;
}
方式二:
Object.assign() 把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。 Object.assign() 进行的是浅拷贝
- 只有一层的情况下是深拷贝
let obj = {name:'cht' , age:18 , hobby:['game','play']}
let copyObj = Object.assign({} , obj);
console.log(copyObj); // {name:'cht' , age:18 , hobby:['game','play']}
copyObj.name = 'hw';
console.log(obj.name); // cht
我们可以发现: 当拷贝后的 copyObj修改name值的时候,obj并没有修改。
- 多层的时候是浅拷贝
let obj = {
name:'cht',
friends:{
name:'hw',
age:15
}
}
let copyObj = Object.assign({} , obj);
console.log(copyObj); // { name: 'cht', friends: { name: 'hw', age: 15 } }
copyObj.friends.name = 'hw1';
console.log(obj); // { name: 'cht', friends: { name: 'hw1', age: 15 } }
我们可以发现: 当拷贝后的 copyObj修改friends中name值的时候,obj发生改变修改。
方式三: Array.prototype.concat()
let arr = [1, 2, {name:'cht' , age:12}];
let copyArr = arr.concat();
console.log(copyArr); // [ 1, 2, { name: 'cht', age: 12 } ]
copyArr[2].name = 'hw';
console.log(arr); // [ 1, 2, { name: 'hw', age: 12 } ]
方式三: Array.prototype.slice()
let arr = [1, 2, {name:'cht' , age:12}];
let copyArr = arr.slice(0);
console.log(copyArr); // [ 1, 2, { name: 'cht', age: 12 } ]
copyArr[2].name = 'hw';
console.log(arr); // [ 1, 2, { name: 'hw', age: 12 } ]
关于slice 和 concat 个人理解:
当数组中是字符串,数字,布尔值的时候。新拷贝数组修改并不会造成原数组的同步修改。
当数组中是引用类型的时候,新拷贝数组修改引用类型中的数据会造成同步修改,也就是上面的两个例子。
# 2.深拷贝的实现
方式一:
1.JSON.parse(JSON.stringify())
let obj = {
name:'cht',
friends:{
name:'hw',
age:15
}
}
let copyObj = JSON.parse(JSON.stringify(obj));
console.log(copyObj); // { name: 'cht', friends: { name: 'hw', age: 15 } }
copyObj.friends.name = 'hw1';
console.log(obj); // { name: 'cht', friends: { name: 'hw', age: 15 } }
原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
缺点:
这种方法虽然可以实现数组或对象深拷贝,但不能处理函数。因为 JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数。
方式二:
简单模拟:浅拷贝 + 递归
// 这是比较简单的实现,也就是 浅拷贝 + 递归。 没有考虑数组等 , 判断忽略了null (null 也是 object),性能页比较差,后面会总结全面的。
function depClone(source) {
let target = {};
for(let i in source) {
if(source.hasOwnProperty(i)) {
if(Object.prototype.toString.call(source[i]) === '[object Object]') {
target[i] = depClone(source[i]);
}else {
target[i] = source[i];
}
}
}
return target;
}
let p = {
name:'cht',
likes:['eat' , 'sing' , 'dance'],
friend:{
name:'hw',
sex: 2
}
}
let p1 = {};
depCopy(p1 , p);
/**
target: 目标对象,
source:被拷贝对象
**/
function depCopy(target , source) {
for(let key in source) {
let sourceVal = source[key];
if(sourceVal instanceof Object) {
let subTarget = new sourceVal.constructor; // 如果 被拷贝对象是一个引用类型,就创建引用类型的实例
target[key] = subTarget; // 将引用类型插入到目标对象中
depCopy(subTarget , sourceVal);
}else {
target[key] = sourceVal;
}
}
}
p1.likes[0] = 'hhhh';
console.log(p1);
console.log(p);