一. 浅拷贝
-
定义:浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
-
图示:
(1)普通对象
1.Object.assign
语法:
Object.assign(target, …sources),第一个参数是拷贝的目标对象,后面的参数是拷贝的来源对象(也可以是多个来源)。
let a = { x: 99, arr: [1, 2] };
let b = Object.assign({},a);
console.log(b == a);//true
b.x = 365;
b.arr[0] = 100;
console.log(b); //{x: 365, arr: [100, 2]}
console.log(a); //{x: 99, arr: [100, 2]}
2.扩展运算符
语法:
let cloneObj = { …obj };
/* 对象的拷贝 */
const obj = {
a: 1,
b: {
c: 1
}
}
const obj2 = {
...obj
}
obj.a = 2
console.log(obj) //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}
obj.b.c = 2
console.log(obj) //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}
(2)数组
1.concat 拷贝数组
const arr = [1, 2, 3, { a: 1 }];
const newArr = arr.concat();
newArr[1] = 0;
newArr[3].a = 4;
console.log(arr); // [ 1, 2, 3, {a: 4} ]
console.log(newArr); // [ 1, 0, 3, {a: 4} ]
2.slice 拷贝数组
语法:
arr.slice(begin, end);
const arr = [1, 2, { val: 4 }];
const newArr = arr.slice();
newArr[2].val = 5;
newArr[1] = 3;
console.log(arr); //[ 1, 2, { val: 5 } ]
console.log(newArr); //[ 1, 3, { val: 5 } ]
3.扩展运算符
let arr = [1, 2, 3, { a: 66 }];
let newArr = [...arr]; //跟arr.slice()是一样的效果
newArr[0] = 99;
newArr[3].a = 88;
console.log(arr); //[1, 2, 3, { a: 88 }]
console.log(newArr); //[99, 2, 3, { a: 88 }]
(3)手写浅拷贝
1.for in 循环
const obj = {
x: 'abc',
y: {
m: 1
}
};
const result = clone2(obj);
function clone2(target) {
// 类型判断:{} [] null
if (typeof target === 'object' && target !== null) {
const res = Array.isArray(target) ? [] : {};
// 遍历target数据
for (let key in target) {
// 判断当前对象上是否包含该属性
if (target.hasOwnProperty(key)) {
// 将属性设置到result结果数据中
res[key] = target[key];
}
}
return res;
} else {
return target;
}
}
result.x = 'xyz';
result.y.m = 2;
console.log(obj); //{x: 'abc', y: {m: 2}}
console.log(result); //{x: 'xyz', y: {m: 2}}
2.扩展运算符
const obj = {
x: 'abc',
y: {
m: 1
}
};
const result = clone1(obj);
function clone1(target) {
// 类型判断:{} [] null
if (typeof target === 'object' && target !== null) {
// 判断如果是数组
if (Array.isArray(target)) {
return [...target];
} else {
return { ...target };
}
} else {
return target;
}
}
result.x = 'xyz';
result.y.m = 2;
// 原对象obj也会受到影响
console.log(obj); //{x: 'abc', y: {m: 2}}
console.log(result); //{x: 'xyz', y: {m: 2}}
二. 深拷贝
- 定义:将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,并且在修改新对象不会影响原对象。
- 图示:
1. JSON.stringify
步骤:把一个对象序列化成为 JSON 的字符串,将对象里面的内容转换成字符串,再用 JSON.parse() 的方法将JSON字符串生成一个新的对象。
注:此方法适用于普通对象和数组的拷贝。
- 普通对象
const obj1 = { a: 1, b: [1, 2, 3] }
const str = JSON.stringify(obj1);
// console.log(str); //{"a":1,"b":[1,2,3]}
const obj2 = JSON.parse(str);
// console.log(obj2); //{a:1,b:[1,2,3]}
obj1.a = 2;
obj1.b[0] = 111;
console.log(obj1); //{a:2,b:[111, 2, 3]}
console.log(obj2); //{a:1,b:[1,2,3]}
- 数组
const obj = [1, 2, 3, {x:99}];
const str = JSON.stringify(obj);
const obj2 = JSON.parse(str);
obj2[0] = 100;
obj2[3].x = 1000;
console.log(obj); //[1, 2, 3, {x: 99}]
console.log(obj2); //[100, 2, 3, {x: 1000}]
- JSON.stringify存在弊端:此处列举2个
const obj = {
x: 666,
y: {
m: 1
},
z: ['e', 'f', 'g'],
// 弊端1:不能克隆方法
d: function () { }
};
// 弊端2:不能循环引用
// obj.z.push(obj.y);
// obj.y.j = obj.z;
const result = deepClone1(obj);
function deepClone1(target) {
// 通过数据创建JSON格式的字符串
let str = JSON.stringify(target);
// 将JSON字符串创建为js数据
let data = JSON.parse(str);
return data;
}
result.y.m = 1000;
console.log(obj);//{x: 666, y: {m: 1}, z: ["e", "f", "g"], d: ƒ}
console.log(result);//{x: 666, y: {m: 1000}, z: ["e", "f", "g"]}
2.手写深拷贝
初级方法:递归
注:【代码为初级版,不能解决循环引用的问题】
const obj = {
x: 666,
y: {
m: 1
},
z: ['e', 'f', 'g'],
d: function() {}
};
// 弊端:不能循环引用
// obj.z.push(obj.y);
// obj.y.j = obj.z;
const result = deepClone2(obj);
function deepClone2(target) {
// 检测数据的类型
if (typeof target === 'object' && target !== null) {
const res = Array.isArray(target) ? [] : {};
for (let key in target) {
if (target.hasOwnProperty(key)) {
res[key] = deepClone2(target[key]);
}
}
return res;
} else {
return target;
}
}
result.x = 999;
result.y.m = 1000;
console.log(obj);//{x: 666, y: {m: 1}, z: ["e", "f", "g"], d: ƒ}
console.log(result);//{x: 999, y: {m: 1000}, z: ["e", "f", "g"], d: ƒ}
高级方法:递归+ map
注:【可以解决循环引用的问题】
const obj = {
x: 666,
y: {
m: 1
},
z: ['e', 'f', 'g'],
d: function() {}
};
// 弊端:不能循环引用
obj.z.push(obj.y);
obj.y.j = obj.z;
const result = deepClone3(obj);
function deepClone3(target, map = new Map()) {
// 检测数据的类型
if (typeof target === 'object' && target !== null) {
let cache = map.get(target);
if (cache) {
return cache;
}
const res = Array.isArray(target) ? [] : {};
// 将新的结果存入到集合中
map.set(target, res);
for (let key in target) {
if (target.hasOwnProperty(key)) {
res[key] = deepClone3(target[key], map);
}
}
return res;
} else {
return target;
}
}
result.y.m = 1000;
console.log(obj);
console.log(result);
三、直接赋值和拷贝的区别
1.普通对象赋值
let obj1 = {
name: 'zhangsan',
age: 18,
data: {x: 1},
arr: [1, 2]
}
let obj2 = obj1;
obj2.name = 'lisi';
obj2.data.x = 99;
obj2.arr[0] = 100;
console.log('obj1:',obj1); //{name: 'lisi', age: 18, data: {x: 99}, arr: [100, 2]}
console.log('obj2:',obj2); //{name: 'lisi', age: 18, data: {x: 99}, arr: [100, 2]}
2.数组赋值
let arr = [1, 2, 3, {x:10}];
let arr2 = arr;
arr2[0] = 99;
arr2[3].x = 1000;
console.log(arr); //[99, 2, 3, {x: 1000}]
console.log(arr2); //[99, 2, 3, {x: 1000}]
总结:对数组或者普通对象直接赋值,不管是对象中元素是基本类型值,还是引用类型值,改变任意一个值,都会对源对象产生影响。
四、总结
- 赋值和深浅拷贝的对比: