介绍
在 JavaScript 中,对象的复制是一个常见的操作。而浅拷贝和深拷贝是两种常用的复制方式,每种方式都有其适用的场景和注意事项。本文将深入探讨浅拷贝和深拷贝的概念,并介绍实现它们的不同方法。
浅拷贝的概念和实现方法及原理
浅拷贝方法基于原对象拷贝得到一个新的对象,原对象中内容的修改会影响新对象
一个形象的比喻就是自己和自己的影子,主体改变了,影子也会跟着变。
ps: 浅拷贝通常只针对引用类型 原始类型只有深拷贝
Object.create(x)
Object.create(x) 是一种创建新对象的方法,其中 x 是新对象的原型,示例代码如下:
// 原始对象
let person = {
name: 'John',
};
// 使用 Object.create() 进行浅拷贝
let shallowCopy = Object.create(person);
// 修改原始对象属性
person.name = 'Andy';
//输出浅拷贝对象
console.log(shallowCopy.name);//name为Andy
Object.assign({},a)
这个方法括号中的内容指的是把逗号后面的对象,拼到括号前面的对象里,即把 a 拼到 {} 里,虽然得到了一个新对象,但是里面的内容还是 a
示例代码:
// 源对象
const a = {
name: '贝贝',
age: 8,
address: {
city: '北京',
}
};
// 使用 Object.assign() 进行浅拷贝
let c = Object.assign({}, a);
// 输出目标对象
console.log(c);
输出结果为
{ name: '贝贝', age: 8, address: { city: '北京' } }
[].concat(x)
.concat() 方法的作用是把括号中的数组合并到前面的数组中,且此方法不会修改原始数组,而是返回一个新的数组的特点,所以我们也可以用这个方法来复制一个数组,即[].concat(x)
示例代码:
let newArr = [].concat(arr)
console.log(newArr)
输出结果为:[ 1, 2, 3, { a: 10 } ]
数组解构
要使用数组解构实现浅拷贝,可以将原始数组解构到一个新的数组变量中。这样做会创建一个新的数组,其中包含与原始数组相同的元素。(先拆开,再合并)
const originalArray = [1, 2, 3, 4, 5];
// 使用数组解构进行浅拷贝
const copiedArray = [...originalArray];//'...'加数组名表示解构该数组,每个元素都独立开
console.log(copiedArray); // 输出 [1, 2, 3, 4, 5]
arr.slice(0)
let arr = [1, 2, 3, 4, 5];
// 使用 slice() 进行浅拷贝
let arr2 = arr.slice(0);//从下标为0开始截出,截到底
console.log(arr2); // 输出 [1, 2, 3, 4, 5]
和.splice()很像的一个方法,功能也很像,这里我们区分一下
arr.splice(1, 1) //括号里的表示:(下标,删几位) ----截掉
arr.slice(1, 3)//左闭右开,此示例指 包1除3 。括号里的表示(下标,截到哪一位/可不写) ---截出来,不影响原数组
arr.toReverser().reverse()
此方法的思路是,先把数组反转并返回一个新数组(.toReverser()),然后再在这个新数组的基础上进行反转(.reverse())
示例代码:
let arr = [1, 2, 3, 4, 5];
let arr2 = arr.toReversed();
console.log(arr2);// 输出: [5, 4, 3, 2, 1]
let arr3 = arr2.reverse()
console.log(arr3);//输出:[ 1, 2, 3, 4, 5 ]
由此我们也可以了解到 .toReverser() 和 .reverse() 方法的区别==>
toReverser()会返回一个新数组,reverse()不会
实现原理
这里我们用代码解释原理:
let obj = {
name: 'Alo',
like: {
a: 'food'
}
}
function shallow(obj) {
let newObj = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {//判断对象元素是否为显示属性
newObj[key] = obj[key]//是就拷贝
}
} //遍历对象 ---隐式具有的属性不拷贝
return newObj
}
console.log(shallow(obj));//{xxx}
对于这段代码我们着重看这两行:
for (let key in obj):
这行使用 for...in 循环遍历参数对象 obj 中的每个属性
newObj[key] = obj[key]:
点(.)表示法适用于已知属性名的情况,例如 obj.key,其中 key 是属性名。
方括号([])表示法适用于属性名是动态计算的情况,例如 obj[key],其中 key 是一个变量,它的值在运行时确定。通过使用方括号表示法,可以根据变量的值来访问对象的属性。
深拷贝的概念和实现方法及原理
深拷贝方法基于原对象拷贝得到一个新的对象,原对象中内容的修改不会影响新对象
这里可以理解为自己和自己的克隆人,克隆人就算嗝屁了,跟自己也没关系,不会有影响
JSON 序列化和反序列化
我们使用7种原始类型进行测试 示例代码:
let obj = {
name: 'pingping',
age: 18,
like: {
n: 'coding'
},
a: true,
b: undefined,
c: null,
d: Symbol(1),
e: 123n,
f: function () {
}
}
let obj2 = JSON.parse(JSON.stringify(obj))//读不了 bigInt
obj.like.n = 'studying'//没变,是coding
console.log(obj2);
运行结果是这样的:
它说它不能识别BigInt类型,好,那我们删掉
e: 123n,这行再运行
出了鬼,没有b,d,f。
好,现在我们可以进行总结了,虽然此方法可以复制,但有以下几个缺点:
缺点:
- 不能识别BigInt类型
- 不能拷贝 undefined Symbol function 类型的值
- 不能处理循环引用
structuredClone()
此方法代码非常简洁:
const obj = { name: "Alo", age: 19};
const clonedObj = structuredClone(obj);
console.log(clonedObj); // { name: "Alo", age: 19}
相较之下真的很方便,BUT!structuredClone() 方法是一个新功能,并不是所有浏览器都支持。在某些浏览器中,可能需要使用其他方法或库来实现深拷贝操作
实现原理
原理采用了递归的思想:
const user = {
name: {
firstName: 'Alo',
lastName: '365'
},
age: 19
}
function deep(obj) {
let newObj = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {//判断是否为显示属性
if (obj[key] instanceof Object) {//typeof (obj[key]) === 'object'&& obj[key] !== null
newObj[key] = deep(obj[key])//递归 --自己调用自己
} else {
newObj[key] = obj[key]
}
}
}
return newObj
}
console.log(deep(user));
- 借助for in遍历 原对象,将原对象的属性值增加在新对象中
- 因为 for in 会遍历到对象隐式具有的属性,通常要使用obj.hasOwnProperty(key)来判断拷贝的属性是不是对象显示具有的
- 如果遍历到的属性值是原始值类型,直接往新对象中赋值,如果是引用类型,递归创建新的子对象
总结
浅拷贝和深拷贝是 JavaScript 中对象复制的两种常见方式。浅拷贝只复制对象或数组的第一层属性或元素,而深拷贝会递归复制所有嵌套属性或元素,创建完全独立的副本。本文介绍了实现浅拷贝和深拷贝的常见方法,并比较了它们的优缺点。
希望本文内容能对你提供帮助,下篇文章再见~