我先说结论
深拷贝与浅拷贝最根本的区别在于内存中存储的东西不同!
我们首先知道 在js中 有堆内存和栈内存
一般基本数据类型必然number string boolen null undefind 已经es6新出的符号都是基本数据类型
这些数据类型通常被保存在栈内存中,而引用数据类型也就是我们经常说的对象 通常这个对象实际的数据都存放在栈内存中如下图
var person1 = {name:'jozo'};
var person2 = {name:'xiaom'};
那么我们都知道js这东西可是一个动态的语言,他和java哪些可不一样
最简单的例子莫过于
var str = "abc";
console.log(str[1]="f"); // f
console.log(str); // abc
这里面的字符串数值依然还是abc 并不是afc这表明基本数据类型不能改变值,但是可以从新赋值
但是引用数据类型则不一样
引用数据类型二如果复制一个对象 并不是直接复制而是复制他的地址 ,并不是开辟一个新的地址开一个新的值。
var a = {}; // a保存了一个空对象的实例
var b = a; // a和b都指向了这个空对象
a.name = 'jozo';
console.log(a.name); // 'jozo'
console.log(b.name); // 'jozo'
b.age = 22;
console.log(b.age);// 22
console.log(a.age);// 22
console.log(a == b);// true
真如同上面的 如果我改了b的值,那么a的值也会受到牵连 可谓牵一发动全身
ok 现在我们说的浅拷贝那就是和这个情况息息相关
先给结论:
浅拷贝是创建一个新的对象,对着原始的对象属性值进行一份拷贝
如果属性是基本数据类型 那么就直接拷贝他 如果引用数据类型的值那么你如果对被拷贝的对象里面的值进行修改
浅拷贝内的值也会跟着修改,因为引用数据类型的值,它只拷贝的是地址。
var a1 = {b: {c: {}};
var a2 = shallowClone(a1); // 浅拷贝方法
a2.b.c === a1.b.c // true 新旧对象还是共享同一块内存
ok 我们来了解了解如何实现浅拷贝的几个通用的方法
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }
或者使用es6的新特性使用
展开运算符…
let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }
concat() slice()都是
但是深拷贝和浅拷贝则不同
深拷贝复制的对象完完全全和前对象没有联系了
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
同样用图理解
他会对引用的数据类型进行一次完完全全的开辟一个新的内存 再也不共用同一个了 这些好了不管是引用数据类型还是基本数据类型 他们都是完全不干扰的
说几种深拷贝的实现形式
第一种也是最重要的 最基本的一种,使用递归的方式做的一个方法
实现原理是遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);
同时还有其他的办法这里总结两个
1.JSON.parse(JSON.stringify())
let arr = [1, 3, {
username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)
这也是利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则因为这两者基于JSON.stringify和JSON.parse处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了。
这里第二种办法
这里讲了三个不同的代表复制的办法,对此我们进行一个总结
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/280220.html