1.对浅拷贝和深拷贝的理解
浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用堆和栈的关系,简单类型Undefined,Null,Boolean,Number和String是存入堆,直接引用,object array 则是存入桟中,只用一个指针来引用值),如果拷贝后的对象发生变化, 原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。
2.浅拷贝:浅拷贝的意思就是只复制引用(指针),而未复制真正的值。
// 数组浅拷贝
let A = [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }];
let B = A;
B[0].name = '张三';
console.log('A', A);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }]
console.log('B', B);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }]
// 数组浅拷贝时,我只想改变B数组的数据,然而这时候A和B数组都变了
// 对象浅拷贝
let a = { id: 1, val: "111", info: { projectName: "项目1" } };
let b = a;
// 给b加一个字段name
b.name = '李四';
console.log('a', a);// { id: 1, val: "111", info: { projectName: "项目1" }, name:"李四" }
console.log('b', b);// { id: 1, val: "111", info: { projectName: "项目1" }, name:"李四" }
效果如图:
从上面可以看出浅拷贝不管是对引用类型进行什么改变,它们都是一起变化的
3.深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。
只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。目前实现深拷贝的方法不多,主要是两种:
方法一:利用 JSON 对象中的 parse 和 stringify
方法二:利用递归来实现每一层都重新创建对象并赋值方法一简单来说就是先通过JSON.stringify 是将一个 JavaScript 值转成一个 JSON 字符串, 然后再通过JSON.parse 将一个 JSON 字符串转成一个 JavaScript 值或对象
let C = [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }];
let D = JSON.parse(JSON.stringify(C));
D.push({ id: 4, name: 'dd' });
console.log('C', C);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }]
console.log('D', D);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }, { id: 4, name: "dd" }]
let c = { id: 1, val: "111", info: { projectName: "项目1" } };
let d = JSON.parse(JSON.stringify(c));
d.val = '666';
console.log('c', c);// { id: 1, val: "111", info: { projectName: "项目1" } }
console.log('d', d);// { id: 1, val: "666", info: { projectName: "项目1" } }
注意:利用JSON.parse(JSON.stringify())方法有局限性,那就是当深拷贝对象含有undefined、function、
symbol 会在转换过程中被忽略,这时候就要用方法二了,例子如下:
let originObj = {
name: '张三',
sayGoodbye: function () {
console.log('Goodbye!');
}
}
let copyObj = JSON.parse(JSON.stringify(originObj));
originObj.sayGoodbye()//Goodbye!
copyObj.sayGoodbye()//Uncaught TypeError: copyObj.sayGoodbye is not a function
如上对象里面的方法,利用JSON.parse(JSON.stringify())方法是没办法拷贝的,所以当你使用这个方法时要考虑你的情况是否适用
方法二递归,递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值 的操作,简单粗暴上代码:
// 递归方法:
function deepClone(source) {
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for (let keys in source) { // 遍历目标
if (source.hasOwnProperty(keys)) {
if (source[keys] && typeof source[keys] === 'object') { // 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
} else { // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
运用上述方法:
let E = { id: 1, val: "111", arr: [1, 2, { name: '嘻嘻' }], info: { projectName: "项目1" } };
let F = deepClone(E);
console.log(E === F); // false
console.log('E', E); // { id: 1, val: "111", arr:[1,2,{name:'嘻嘻'}], info: { projectName: "项目1" } }
console.log('F', F); // { id: 1, val: "111", arr:[1,2,{name:'嘻嘻'}], info: { projectName: "项目1" } }
let e = {
name: '张三',
sayGoodbye: function () {
console.log('Goodbye!');
}
};
let f = deepClone(e);
e.sayGoodbye();//Goodbye!
f.sayGoodbye();//Goodbye!
用递归方法可以完美的实现深拷贝!
4.扩展:在 JavaScript 中,数组有两个方法 concat 和 slice 是可以实现对原数组的拷贝的,这两个方法都不会修改原数组,而是返回一个修改后的新数组。同时,ES6 中 引入了 Object.assgn 方法和 … 展开运算符也能实现对对象的拷贝
concat:该方法可以连接两个或者更多的数组,但是它不会修改已存在的数组,而是返回一个新数组。
let originArr = [1, [1, 2, 3], {
a: 1,
sayGoodbye: function () {
console.log('Goodbye!');
}
}];
let copyArr = originArr.concat();
console.log(copyArr === originArr); // false
copyArr[2].sayGoodbye();//Goodbye!
copyArr[1].push(4);
copyArr[2].a = 2;
console.log('originArr',originArr); // [1, [1, 2, 3, 4], {a: 2,sayGoodbye: function () {console.log('Goodbye!');}}]
console.log('copyArr',copyArr); // [1, [1, 2, 3, 4], {a: 2,sayGoodbye: function () {console.log('Goodbye!');}}]
originArr 中含有数组 [1,2,3] 和对象 {a:1},如果我们直接修改数组和对象,不会影响 originArr,但是我们修改数组 [1,2,3] 或对象 {a:1} 时,发现 originArr 也发生了变化。
结论: concat 只是对数组的第一层进行深拷贝。
剩下几个我就不举例子了,直接上结论:
结论: slice 也是只对数组的第一层进行深拷贝。
结论:… 实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。
结论:Object.assign() 拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
最后总结一下:
1.赋值运算符 = 实现的是浅拷贝,只拷贝对象的引用值;
2.JavaScript 中数组和对象自带的拷贝方法都是“首层浅拷贝”;
3.JSON.stringify 实现的是深拷贝,但是对目标对象有要求(非 undefined,function);
4.若想真正意义上的深拷贝,请递归。
希望文档能帮助到您,最后求个赞,谢谢~
下面是例子完整代码和截图有需要的自己拷贝:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>详细解读深浅拷贝</title>
</head>
<body>
<script>
// 1.对浅拷贝和深拷贝的理解
// 浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用
// (堆和栈的关系,简单类型Undefined,Null,Boolean,Number和String是存入堆,
// 直接引用,object array 则是存入桟中,只用一个指针来引用值),如果拷贝后的对象发生变化,
// 原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。
// 2.浅拷贝:浅拷贝的意思就是只复制引用(指针),而未复制真正的值。
// 数组浅拷贝
let A = [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }];
let B = A;
B[0].name = '张三';
console.log('A', A);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }]
console.log('B', B);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }]
// 数组浅拷贝时,我只想改变B数组的数据,然而这时候A和B数组都变了
// 对象浅拷贝
let a = { id: 1, val: "111", info: { projectName: "项目1" } };
let b = a;
// 给b加一个字段name
b.name = '李四';
console.log('a', a);// { id: 1, val: "111", info: { projectName: "项目1" }, name:"李四" }
console.log('b', b);// { id: 1, val: "111", info: { projectName: "项目1" }, name:"李四" }
// 从上面可以看出浅拷贝不管是对引用类型进行什么改变,它们都是一起变化的
// 3.深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。
// 只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。目前实现深拷贝的方法不多,主要是两种:
// 方法一:利用 JSON 对象中的 parse 和 stringify
// 方法二:利用递归来实现每一层都重新创建对象并赋值
// 方法一简单来说就是先通过JSON.stringify 是将一个 JavaScript 值转成一个 JSON 字符串,
// 然后再通过JSON.parse 将一个 JSON 字符串转成一个 JavaScript 值或对象
let C = [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }];
let D = JSON.parse(JSON.stringify(C));
D.push({ id: 4, name: 'dd' });
console.log('C', C);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }]
console.log('D', D);// [{ id: 1, name: "aa" }, { id: 2, name: "bb" }, { id: 3, name: "cc" }, { id: 4, name: "dd" }]
let c = { id: 1, val: "111", info: { projectName: "项目1" } };
let d = JSON.parse(JSON.stringify(c));
d.val = '666';
console.log('c', c);// { id: 1, val: "111", info: { projectName: "项目1" } }
console.log('d', d);// { id: 1, val: "666", info: { projectName: "项目1" } }
// 注意:利用JSON.parse(JSON.stringify())方法有局限性,那就是当深拷贝对象含有undefined、function、
// symbol 会在转换过程中被忽略,这时候就要用方法二了,例子如下:
let originObj = {
name: '张三',
sayGoodbye: function () {
console.log('Goodbye!');
}
}
let copyObj = JSON.parse(JSON.stringify(originObj));
originObj.sayGoodbye()//Goodbye!
// copyObj.sayGoodbye()//Uncaught TypeError: copyObj.sayGoodbye is not a function
// 如上对象里面的方法,利用JSON.parse(JSON.stringify())方法是没办法拷贝的,所以当你使用这个方法时要考虑你的情况是否适用
// 方法二递归,递归的思想就很简单了,就是对每一层的数据都实现一次 创建对象->对象赋值 的操作,简单粗暴上代码:
// 方法:
function deepClone(source) {
const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
for (let keys in source) { // 遍历目标
if (source.hasOwnProperty(keys)) {
if (source[keys] && typeof source[keys] === 'object') { // 如果值是对象,就递归一下
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
} else { // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
let E = { id: 1, val: "111", arr: [1, 2, { name: '嘻嘻' }], info: { projectName: "项目1" } };
let F = deepClone(E);
console.log(E === F); // false
console.log('E', E); // { id: 1, val: "111", arr:[1,2,{name:'嘻嘻'}], info: { projectName: "项目1" } }
console.log('F', F); // { id: 1, val: "111", arr:[1,2,{name:'嘻嘻'}], info: { projectName: "项目1" } }
let e = {
name: '张三',
sayGoodbye: function () {
console.log('Goodbye!');
}
};
let f = deepClone(e);
e.sayGoodbye();//Goodbye!
f.sayGoodbye();//Goodbye!
// 完美深拷贝!
// 4.扩展:在 JavaScript 中,数组有两个方法 concat 和 slice 是可以实现对原数组的拷贝的,
// 这两个方法都不会修改原数组,而是返回一个修改后的新数组。同时,ES6 中 引入了 Object.assgn 方法和 ... 展开运算符
// 也能实现对对象的拷贝
// concat:该方法可以连接两个或者更多的数组,但是它不会修改已存在的数组,而是返回一个新数组。
let originArr = [1, [1, 2, 3], {
a: 1,
sayGoodbye: function () {
console.log('Goodbye!');
}
}];
let copyArr = originArr.concat();
console.log(copyArr === originArr); // false
copyArr[2].sayGoodbye();//Goodbye!
copyArr[1].push(4);
copyArr[2].a = 2;
console.log('originArr',originArr); // [1, [1, 2, 3, 4], {a: 2,sayGoodbye: function () {console.log('Goodbye!');}}]
console.log('copyArr',copyArr); // [1, [1, 2, 3, 4], {a: 2,sayGoodbye: function () {console.log('Goodbye!');}}]
// originArr 中含有数组 [1,2,3] 和对象 {a:1},如果我们直接修改数组和对象,不会影响 originArr,
// 但是我们修改数组 [1,2,3] 或对象 {a:1} 时,发现 originArr 也发生了变化。
// 结论: concat 只是对数组的第一层进行深拷贝。
// 结论: 同样的例子就不写了, slice 也是只对数组的第一层进行深拷贝。
// 结论:... 实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。
// 结论:Object.assign() 拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
// 最后总结一下:
// 1.赋值运算符 = 实现的是浅拷贝,只拷贝对象的引用值;
// 2.JavaScript 中数组和对象自带的拷贝方法都是“首层浅拷贝”;
// 3.JSON.stringify 实现的是深拷贝,但是对目标对象有要求(非 undefined,function);
// 4.若想真正意义上的深拷贝,请递归。
</script>
</body>
</html>