目录
1.JSON.parse(JSON.stringify())
一.什么是深拷贝浅拷贝
数据类型分为简单数据类型和复杂数据类型,两者存储数据的方式不同:
- 基本数据类型:是直接存储在栈里面的。
- 引用数据类型:是在栈里面存放一个地址,真实的数据存储在堆内存里面,地址指向这个真实数据。
深拷贝和浅拷贝是指复制引用数据类型(下面统指对象)时的两种不同的方法。
1.浅拷贝
创建一个新的对象,新对象的属性和原始对象相同,如果属性是基本数据类型,拷贝的就是基本数据类型存在栈里的真实数据,两个对象的该属性不相互影响。如果数据是引用数据类型,拷贝的就是地址,因此如果该属性的值发生改变,那么另外一个对象的该属性也会随之发生改变。
可以理解为只拷贝第一层的真实数据,再深入就是引用地址。
换做数组就是,如果元素是基本数据类型,拷贝的就是基本数据类型存在栈里的真实数据,两个数组的该元素不互相影响。如果元素是引用数据类型,拷贝的就是地址,因此如果该元素的值发生改变,那么另外一个数组的该元素也会随之发生改变。
let obj1 = {
name: "于家宝",
body: {
age: 18,
},
};
// 拓展运算符是浅拷贝的一种方式
let obj2 = { ...obj1 };
obj1.name = "yujiabao"; // 该属性name为基本数据,拷贝的是真实数据,因此两个对象的该属性不会相互影响
obj1.body.age = 19; // 该属性body为引用数据,拷贝的是地址,指向同一个真实数据,一个对象的该属性改变,另外一个对象的该属性也会随之改变
console.log(obj2);
还有一种情况不要混淆!!!:
let obj1 = {
name: "于家宝",
body: {
age: 18,
},
};
// 拓展运算符是浅拷贝的一种方式
let obj2 = { ...obj1 };
obj1.name = "yujiabao"; // 该属性name为基本数据,复制的是真实数据,因此两个对象的该属性不会相互影响
obj1.body = {
age: 19,
}; // 该属性body为引用数据,但是这样是直接改变了该属性的地址,因此两个对象的地址不一样,不指向同一个数据,所以两个对象的该属性不会相互影响
console.log("obj1", obj1);
console.log("obj2", obj2);
2. 深拷贝
创建一个新对象,新对象和原始对象的属性完全相同但是指向不同的地址,修改其中一个不会影响另外一个。
可以理解为递归的去拷贝了每一层的真实数据。
let obj1 = {
name: "yujiabao",
body: {
age: 18,
},
};
// JSON.stringify是深拷贝的一种方式
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.name = "于家宝";
obj1.body.age = 19; // 无论什么属性,新旧对象都不会互相产生影响
console.log("obj1", obj1);
console.log("obj2", obj2);
二.浅拷贝的实现方式
1.拓展运算符
let obj1 = {
name: "yujiabao",
body: {
age: 18,
},
};
let obj2 = { ...obj1 };
obj1.name = "于家宝";
obj1.body.age = 19;
console.log(obj2.name); // yujiabao 不影响
console.log(obj2.body.age); // 19 影响
2.Object.assign()
let obj1 = {
name: "yujiabao",
body: {
age: 18,
},
};
let obj2 = Object.assign({}, obj1);
obj1.name = "于家宝";
obj1.body.age = 19;
console.log(obj2.name); // yujiabao 不影响
console.log(obj2.body.age); // 19 影响
3.Array.prototype.concat
let arr1 = [1, 2, [3, 4], 5];
let arr2 = arr1.concat();
arr1[0] = 6;
arr1[2][0] = 7;
console.log(arr2[0]); // 1 不影响
console.log(arr2[2][0]); // 7 影响
4.Array.prototype.slice()
let arr1 = [1, 2, [3, 4], 5];
let arr2 = arr1.slice();
arr1[0] = 6;
arr1[2][0] = 7;
console.log(arr2[0]); // 1 不影响
console.log(arr2[2][0]); // 7 影响
5.Array.from()
let arr1 = [1, 2, [3, 4], 5];
let arr2 = Array.from(arr1);
arr1[0] = 6;
arr1[2][0] = 7;
console.log(arr2[0]); // 1 不影响
console.log(arr2[2][0]); // 7 影响
6.手写实现浅拷贝
function shallowCopy (params) {
// 基本类型直接返回
if (!params || typeof params !== "object") return params;
// 根据 params 的类型判断是新建一个数组还是对象
let newObject = Array.isArray(params) ? [] : {};
// 遍历 params 并判断是 params 的属性才拷贝
for (let key in params) {
if (params.hasOwnProperty(key)) {
newObject[key] = params[key];
}
}
return newObject;
}
let params = { a: 1, b: { c: 1 } }
let newObj = shallowCopy(params)
params.a = 222
params.b.c = 666
console.log(params); // { a: 222, b: { c: 666 } }
console.log(newObj); // { a: 1, b: { c: 666 } }
7.函数库lodash提供的clone()方法
三.深拷贝的实现方式
1.JSON.parse(JSON.stringify())
有个缺点是不接受函数,undefined,symbol。
let obj1 = {
name: "yujiabao",
body: {
age: 18,
},
};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.name = "于家宝";
obj1.body.age = 19;
console.log(obj2.name); // yujiabao 不影响
console.log(obj2.body.age); // 18 不影响
2. jQuery.extend()方法
$.extend(deepCopy, target, object1, [objectN])//第一个参数为true,就是深拷贝
var $ = require('jquery');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = $.extend(true, {}, obj1);
3.手写循环递归
简单的:无法实现对函数 、RegExp等特殊对象的克隆,会抛弃对象的constructor,所有的构造函数会指向Object,对象有循环引用,会报错;
function deepCopy(object) {
// 只拷贝对象
if (!object || typeof object !== "object") return;
// 根据 object 的类型判断是新建一个数组还是对象
let newObj = Array.isArray(object) ? [] : {};
// 遍历 object,并且判断是 object 的属性才拷贝
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObj[key] = typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
}
}
return newObj;
}
进阶版:
function deepClone(target) {
const map = new WeekMap();
function isObject(target) {
return (typeof target === "object" && target) || (typeof target === "function");
}
function clone(data) {
if (!isObject(target)) {
return;
}
if ([Date, RegExp].includes(data.constructor)) {
return new data.constructor(data);
}
if (typeof data === "function") {
return new Function("return " + data.toString())();
}
const exist = map.get(data);
if (exist) {
return exist;
}
if (data instanceof Map) {
const result = new Map();
map.set(data, result);
data.forEach((val, key) => {
if (isObject(val)) {
result.set(key, clone(val));
} else {
result.set(key, val);
}
});
return result;
}
if (data instanceof Set) {
const result = new Set();
map.set(data, result);
data.forEach(val => {
if (isObject(val)) {
result.add(clone(val));
} else {
result.add(val);
}
});
return result;
}
const keys = Reflect.ownKeys(data);
const allDesc = Object.getOwnPropertyDescriptors(data);
const result = Object.create(Object.getPrototypeOf(data), allDesc);
map.set(data, result);
keys.forEach(key => {
const val = data[key];
if (isObject(val)) {
result[key] = clone(val);
} else {
result[key] = val;
}
});
return result;
}
return clone(target);
}
4.structuredClone()
let obj = { a: 1, b: { c: 2 }, d: [3, 4], e: new Date() };
let deepCopy = structuredClone(obj);
deepCopy.b.c = 100;
deepCopy.d[0] = 300;
console.log(obj.b.c); // 输出: 2
console.log(obj.d[0]); // 输出: 3
console.log(deepCopy.b.c); // 输出: 100
console.log(deepCopy.d[0]); // 输出: 300
console.log(deepCopy.e); // Thu Jan 02 2025 17:36:26 GMT+0800 (中国标准时间)
5.函数库loadsh提供的cloneDeep()方法
四.浅拷贝和 = 复制的区别
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
举个例子:
let obj1 = {
name: "于家宝",
body: {
age: 18,
},
};
let obj2 = Object.assign({}, obj1);
obj1.name = "yujiabao";
obj1.body.age = 19;
console.log(obj2);
浅拷贝的话 基础类型 name 没有改变,引用类型body里面的 age改变。
let obj1 = {
name: "于家宝",
body: {
age: 18,
},
};
let obj2 = obj1;
obj1.name = "yujiabao";
obj1.body.age = 19;
console.log(obj2);
赋值的话 基础类型和引用类型都改变了。