关于深拷贝和浅拷贝的个人理解
关于深拷贝和浅拷贝的个人理解
一、深拷贝和浅拷贝区别
如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。
示例:
let obj = {
name: "hahah",
age: 18,
sex: "男",
}
// 复杂数据类型 赋值相当于是 赋引用地址
const newObj = obj;
// 修改newObj其中一个值 , 另一个obj也发生改变
newObj.name = "hahahahahahah";
console.log(obj, newObj);
输出结果:两个复杂数据类型的 name 值全部改变 ,复杂数据类型 = 赋值只是 引用地址的传递 ,指向的还是相同的引用。
简单的第一层浅拷贝:
let obj = {
name: "lili",
age: 18,
sex: "男",
arr: [1, "2", "3"],
obj: {
name: "hahaha",
sex: "女",
}
};
// 方法 一
// let newObj = Object.assign({}, obj);
// 方法 二
let newObj = { ...obj };
// 再次改变时就是 改变不一样的数据
newObj.name = "hahaha";
newObj.obj.sex = "男";
newObj.arr[1] = 10;
console.log(obj, newObj);
输出结果:可以看到第一层的 name 没有互相影响 ,但是更深层次的 arr 数组 和 obj 对象 还是发生了相同改变,这还是一个浅拷贝。我们可以使用很多方式在js中复制数据,比如 扩展运算符...,Object.assign, Object.freeze,slice, concat, map,filter, reduce等方式进行复制,
这里可以得出一个结论:浅拷贝,就是只拷贝第一层数据,更深层的数据还是同一个引用。
二、简单数据类型(基本数据类型)
如果是基本数据类型,名字和值都会储存在栈内存中
简单数据类型可以随意赋值都不会相互影响。
三、复杂数据类型(引用数据类型)
复杂数据类型的存储方法:存储空间分为堆空间 和 栈空间 ,复杂数据类型的值存在于 堆空间中 ,而名(obj)则存在于栈空间中 ,堆空间返回一个引用地址给 名 ,名根据引用地址去访问 栈空间中的值。
图示:
当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。
此时 a 和 b 指向的是 堆空间的同一个地址。而当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。
顺着往下想可以得到这么一个结论:在堆内存中也开辟一个新的内存专门为b存放值,就像基本类型那样,岂不就达到深拷贝的效果了 。
四、实现深拷贝的方法
1、for in 循环实现深拷贝
let obj = {
name: "hahaha",
age: 18,
objChild: {
sex: "男",
addres: "深圳",
arr: [1, 2, 3, "10", "12", { aaa: "aaa", bbb: "bbb" }],
},
func: function () {
console.log("我是一个函数");
}
}
function simpleCopy(obj) {
// 判断 obj 为数组还是对象
let newObj = Array.isArray(obj) ? [] : {}
// 遍历该对象
for (let i in obj) {
// 判断 该对象是否 真的包含 i 这个键
if (obj.hasOwnProperty(i)) {
// 判断 obj[i] 是否为 对象或者数组
if (obj[i] && (typeof obj[i]) === "object") {
// 是对象递归再次执行
newObj[i] = simpleCopy(obj[i]);
} else {
// 不是直接简单赋值
newObj[i] = obj[i];
}
}
}
return newObj;
}
let newObj = simpleCopy(obj);
newObj.name = "ccccc";
newObj.objChild.sex = "女";
newObj.objChild.arr[5].aaa = "小帅";
newObj.func = () => {
console.log("hahahahahahah");
}
obj.func(); // 我是一个函数
newObj.func(); // hahahahahahah
console.log(obj);
console.log(newObj);
console.log(newObj === obj); // false
输出结果:可以看到拷贝后的修改并没有影响到原先 对象 ,新的对象的引用地址已经改变,与原先对象属于两个不同对象,此时 console.log(newObj === obj); // false
2、JSON.stringfiy和JSON.parse方法实现深拷贝
let obj = {
name: "hahaha",
age: 18,
objChild: {
sex: "男",
addres: "深圳",
arr: [1, 2, 3, "10", "12", { aaa: "aaa", bbb: "bbb" }],
func: function () {
console.log("我是一个函数");
}
},
}
function simpleCopy(obj) {
let objClone = JSON.parse(JSON.stringify(obj));
return objClone;
}
let newObj = simpleCopy(obj);
newObj.name = "ccccc";
newObj.objChild.sex = "女";
newObj.objChild.arr[5].aaa = "小帅";
newObj.func = () => {
console.log("hahahahahahah");
}
console.log(obj);
console.log(newObj);
console.log(newObj === obj); // false
obj.objChild.func(); // 我是一个函数
newObj.objChild.func(); // 报错
输出结果:
**缺点: 无法实现对对象中方法的深拷贝,会显示为undefined **
3、通过jQuery的extend方法实现深拷贝
let newObj = $.extend(true, {}, obj); // true为深拷贝,false为浅拷贝
4、lodash函数库实现深拷贝
let result = _.cloneDeep(test)
官网截图:
5、Reflect法(与 for in 遍历类似)
// 代理法
function deepClone(obj) {
// 判断是否为一个对象
if (!isObject(obj)) {
throw new Error('obj 不是一个对象!')
}
let isArray = Array.isArray(obj)
let cloneObj = isArray ? [...obj] : { ...obj }
// 静态方法 Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组。
Reflect.ownKeys(cloneObj).forEach(key => {
cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
})
return cloneObj
}
6、手动实现深拷贝
let obj1 = {
a: 1,
b: 2
}
let obj2 = {
a: obj1.a,
b: obj1.b
}
obj2.a = 3;
alert(obj1.a); // 1
alert(obj2.a); // 3
7、Object.assign来实现深拷贝
// 只适用于只有一层复杂数据类型
var obj = {
a: 1,
b: 2
}
var obj1 = Object.assign({}, obj); // obj赋值给一个空{}
obj1.a = 3;
console.log(obj.a);// 1
8、数组方法slice实现对数组的深拷贝
// 当数组里面的值是基本数据类型,比如String,Number,Boolean时,属于深拷贝
// 当数组里面的值是引用数据类型,比如Object,Array时,属于浅拷贝
var arr1 = ["1","2","3"];
var arr2 = arr1.slice(0);
arr2[1] = "9";
console.log("数组的原始值:" + arr1 );
console.log("数组的新值:" + arr2 );
9、数组方法concat实现对数组的深拷贝
// 当数组里面的值是基本数据类型,比如String,Number,Boolean时,属于深拷贝
var arr1 = ["1","2","3"];
var arr2 = arr1.concat();
arr2[1] = "9";
console.log("数组的原始值:" + arr1 );
console.log("数组的新值:" + arr2 );
// 当数组里面的值是引用数据类型,比如Object,Array时,属于浅拷贝
var arr1 = [{a:1},{b:2},{c:3}];
var arr2 = arr1.concat();
arr2[0].a = "9";
console.log("数组的原始值:" + arr1[0].a ); // 数组的原始值:9
console.log("数组的新值:" + arr2[0].a ); // 数组的新值:9
10、使用扩展运算符实现深拷贝
// 当value是基本数据类型,比如String,Number,Boolean时,是可以使用拓展运算符进行深拷贝的
// 当value是引用类型的值,比如Object,Array,引用类型进行深拷贝也只是拷贝了引用地址,所以属于浅拷贝
var car = {brand: "BMW", price: "380000", length: "5米"}
var car1 = { ...car, price: "500000" }
console.log(car1); // { brand: "BMW", price: "500000", length: "5米" }
console.log(car); // { brand: "BMW", price: "380000", length: "5米" }
11、Object.create()实现深拷贝
function deepClone(initalObj, finalObj) {
var obj = finalObj || {};
for (var i in initalObj) {
var prop = initalObj[i]; // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
if(prop === obj) {
continue;
}
if (typeof prop === 'object') {
obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
} else {
obj[i] = prop;
}
}
return obj;
}





