拷贝指的是将一个对象的内容复制到另一个对象中。在JavaScript中,拷贝指的是将一个对象的内容复制到另一个对象中,拷贝分为浅拷贝和深拷贝且通常只针对引用类型。浅拷贝只拷贝一层对象,它会复制原始值以及引用类型数据的指针。而深拷贝会层层拷贝,它会将所有类型的属性值都会被复制。
浅拷贝
浅拷贝是指通过方法创建一个新对象把某个对象完整拷贝,拷贝后的新对象中的每个元素都是原始对象中相应元素的引用,即原对象的修改会影响新的对象。
在JavaScript中,原始类型的值直接存在调用栈中而引用类型的值是存放在堆中的,引用类型在调用栈里存放的只是堆中的引用地址。所以当对原始类型进行拷贝时,实际上是将原始值从一个变量复制到另一个变量,而不涉及引用。
常见的浅拷贝方法
- Object.create(obj) // 创建一个对象
let a = {
name:'张三'
}
let b =Object.create(a)
a.name='李四'
console.log(b.name); //输出: 李四
- Object.assign({}, obj) // 对象拼接方法
let a ={
name:'张三',
info:{
age:18
}
}
let b=Object.assign({},a)
a.info.age=20
console.log(b)//输出: { name: '张三', info: { age: 20 } }
- [].concat(arr) // 数组拼接方法
let arr = [1,2,3,{n:10}]
let newArr = [].concat(arr)
arr[3].n =100
console.log(newArr);
- 数组解构
let arr = [1,2,3,{n:10}]
let newArr=[...arr]
arr[3].n =100
console.log(newArr);
- slice() //数组剪切方法
const arr = [1, 2, 3, 4, 5];
const newArr = arr.slice();
console.log(newArr); // [1, 2, 3, 4, 5]
浅拷贝的实现
let obj={
a:1,
b:{
n:2
},
c:[1,2,3,4]
}
let obj2 = shallowCopy(obj);
function shallowCopy(obj) {
let obj2 = {};
for (let key in obj) {
if(obj.hasOwnProperty(key)){
obj2[key] = obj[key];
}
}
return obj2;
}
obj2.b.n=100
console.log(obj); // 输出为:{ a: 1, b: { n: 100 }, c: [ 1, 2, 3, 4 ] }
console.log(obj2);// 输出为:{ a: 1, b: { n: 100 }, c: [ 1, 2, 3, 4 ] }
深拷贝
深拷贝是指通过方法创建一个新对象把某个对象完整拷贝,拷贝后的新对象中的内容是复制的原始对象的内容,新对象中的每个元素都是原始对象中相应元素的复制。所以修改新对象中的元素不会影响到原始对象中的对应元素。
常见的深拷贝方法
- JSON.parse(JSON.stringify(obj))
当使用JSON.parse(JSON.stringify(obj)) 对对象进行深拷贝时,会将对象转换为 JSON 字符串,然后再将 JSON 字符串转换回对象,从而实现深层次的拷贝。
let obj = {
name: "张三",
info: {
age: 25,
sex: "男"
}
};
// 使用 JSON.parse(JSON.stringify(obj)) 进行深拷贝
let obj2 = JSON.parse(JSON.stringify(obj));
// 修改新对象中的属性的值
obj2.info.age=18;
// 输出原始对象和克隆后的对象
console.log(obj); // 输出: { name: '张三', info: { age: 25, sex: '男' } }
console.log(obj2); // 输出: { name: '张三', info: { age: 18, sex: '男' } }
值得注意的是:
- 这个方法不能处理underfined,function,Symbol这些数据类型
- 也无法处理循环引用。
这个方法可以使用大部分情况,如果遇到以上两种问题可以使用lodash的深拷贝函数
- structuredClone(obj)
let obj = {
a: 1,
b: {
n: 2
},
c: [1, 2, 3, 4]
}
const obj2=structuredClone(obj)
structuredClone() 方法是用于复制一个对象,包括其所有属性和嵌套对象,而不会受限于像 JSON.stringify() 和 JSON.parse() 那样只能处理一部分 JavaScript 对象的限制。
值得注意的是structuredClone(obj)无法兼容symbol 和function。
深拷贝的实现
- 递归
let obj = {
name: '张三',
age: 18,
a: {
n: 1
},
b: undefined,
c: null,
d: function () { },
e: Symbol('hello'),
f: {
n: 100
}
}
function deepCopy(obj) {
let objCopy = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
//区分obj[key]是原始类型或者引用类型
if (obj[key] instanceof Object) {// 不能直接赋值
objCopy[key] = deepCopy(obj[key]) // 递归调用
} else {
objCopy[key] = obj[key]
}
}
}
return objCopy
}
let obj2 = deepCopy(obj)
obj.age = 20;
console.log(obj2);
通过遍历对象的属性,并利用递归来处理嵌套对象,从而实现了对象的深拷贝。
- MessageChannel()
let obj = {
a: 1,
b: {
n: 2
},
c: [1, 2, 3, 4]
}
function deepCLone(obj) {
return new Promise((resolve) => {
const { port1, port2 } = new MessageChannel();
port1.postMessage(obj)
port2.onmessage = (msg) => {
resolve(msg.data)
}
})
}
deepCLone(obj).then((res) => {
obj2 = res
console.log(obj2);
})
在这个函数中,使用 MessageChannel 创建了两个端口 port1 和 port2,然后利用其中一个端口将对象 obj 发送给另一个端口。当另一个端口接收到消息时,将其作为深拷贝后的对象返回。通过 MessageChannel 实现的深拷贝可以处理对象包含循环引用的情况。
总结
需要注意的是,深拷贝会消耗更多的内存和时间,而在处理简单对象结构、不需要深层嵌套对象的情况下,浅拷贝是一个简单而有效的工具。总之,在进行拷贝操作时,需要根据实际情况选择合适的方式来确保程序的正确性和性能。