序言
JavaScript中的拷贝操作分为两种——深拷贝
和浅拷贝
,这个概念其实很简单,在我们日常写代码经常会用到拷贝这个操作,但是我们自己有可能不知道,接下来我会为大家详细介绍JavaScript中的拷贝操作。
浅拷贝 vs 深拷贝(概念介绍)
浅拷贝
浅拷贝只复制对象或数组的一层结构,不涉及嵌套结构内部的对象或数组。这种拷贝方式通常使用 Object.assign()
或扩展运算符(...
)实现,在下面我会为大家对其他方法进行详细介绍。
let obj1 = { a: 1, b: { c: 2 } };
let obj2 = Object.assign({}, obj1);
console.log(obj2);//{ a: 1, b: { c: 2 } }
浅拷贝的特点
我们知道对象这种数据结构属性是存放在堆中的,所以在调用栈中我们只能访问到这个对象的引用地址,当我们通过 Object.assign()
或扩展运算符(...
)等方法实现浅拷贝,我们其实是将对象的引用地址赋给了这个新对象,所以当原对象中的属性改变时,新对象的属性也会跟着改变。
深拷贝
深拷贝是指创建一个全新的对象,该对象与原始对象具有相同的值,但是是独立的,对其进行修改不会影响原始对象。实现深拷贝的方法包括递归遍历和使用专门的库(如 lodash 的 _.cloneDeep
)。
function deepCopy(obj) {
if(typeof obj !== 'object' || obj == null) return //只拷贝引用类型 去除number和null类型
let copy = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if(obj[key] instanceof object){ // obj[key]是不是引用类型
objCopy[key] = deepCopy(obj[key])
}else{
objCopy[key] = obj[key]
}
}
}
return copy;
}
let obj1 = { a: 1, b: { c: 2 } };
let obj2 = deepCopy(obj1);
深拷贝的特点
我们知道既然有浅拷贝,那么对应的就肯定有深拷贝,那么相较于浅拷贝,我们是不是又能猜到深拷贝的一些特点呢。比如说浅拷贝中一旦原对象里的属性发生改变,拷贝的对象中的属性也会跟着改变,相比之下深拷贝中如果原对象发生改变,新对象中的属性是不会发生改变的。
但是常见的深拷贝方法不多,只有三种。在下面我会为大家详细进行介绍。并且地,我们对原始数据进行拷贝一定是深拷贝,所以我们一般不考虑原始数据类型的深拷贝。
2. 常用的浅拷贝方法
1. Object.create(x)
Object.create
方法是一种创建新对象并将现有对象作为其原型的方式。这样做可以实现原对象的浅拷贝。让我们看一个简单的例子:
javascriptCopy code
const originalObj = { key: 'value' };
const copiedObj = Object.create(originalObj);
console.log(copiedObj.key); // 输出 'value'
这里,copiedObj
是 originalObj
的浅拷贝。但需要注意的是,copiedObj
的原型是 originalObj
,所以在访问属性时,如果 copiedObj
本身没有该属性,将会在原型链上查找。
2. Object.assign({}, x)
Object.assign
方法将所有可枚举属性从一个或多个源对象复制到目标对象,并返回目标对象。通过传递一个空对象作为目标对象,我们可以实现对源对象的浅拷贝:
javascriptCopy code
const originalObj = { key: 'value' };
const copiedObj = Object.assign({}, originalObj);
console.log(copiedObj.key); // 输出 'value'
这里,copiedObj
是 originalObj
的浅拷贝。需要注意的是,如果源对象有嵌套的对象或数组,仅会复制它们的引用而不是递归复制。
3. concat
对于数组,concat
方法可以用于连接两个或多个数组,并返回一个新数组。通过传递一个空数组并使用 concat
,我们可以实现对原数组的浅拷贝:
javascriptCopy code
const originalArray = [1, 2, 3];
const copiedArray = [].concat(originalArray);
console.log(copiedArray); // 输出 [1, 2, 3]
这里,copiedArray
是 originalArray
的浅拷贝。与 Object.assign
类似,concat
也只会复制数组中元素的引用。
4. slice
数组的 slice
方法可以从一个已有的数组中返回选定的元素,形成一个新数组。通过调用 slice
方法并不传递任何参数,我们可以实现对原数组的浅拷贝:
javascriptCopy code
const originalArray = [1, 2, 3];
const copiedArray = originalArray.slice();
console.log(copiedArray); // 输出 [1, 2, 3]
这里,copiedArray
是 originalArray
的浅拷贝。与 concat
类似,slice
也只会复制数组中元素的引用。
5. 数组解构
使用数组解构也可以实现对数组的浅拷贝:
javascriptCopy code
const originalArray = [1, 2, 3];
const [...copiedArray] = originalArray;
console.log(copiedArray); // 输出 [1, 2, 3]
这里,copiedArray
是 originalArray
的浅拷贝。数组解构语法将原数组中的元素逐个解构并赋值给新数组,同样只复制元素的引用。
6. arr.toReversed().reverse()
let arr = [1,2,3,{a:10}]
let newArr = arr.toReversed().reverse()
arr[3].a=100
console.log(newArr); //[1,2,3,{a:100}]
这里我们也可以用toReserved()
将调用该方法的数组对象的元素以相反的顺序调换,并返回一个新数组。我们这里直接去浏览器运行而不是NODE环境,因为这是新增的一种方法,node环境是无法读懂这个方法的,甚至GPT3.5目前也无法读懂,但是我们可以去MDN网站中对这个方法进行详细的了解。还是可以用的。
总结
浅拷贝
是一种创建目标对象或数组的一级副本的方式。通过了解一些常见的浅拷贝方法,我们可以根据具体的场景选择合适的方法。需要注意的是,这些方法只复制了第一层的数据,对于嵌套的对象或数组,仅复制了它们的引用。在处理深层次的数据结构时,可能需要考虑使用深拷贝方法来确保所有层次的数据都被复制而不是共享引用。
3. 深拷贝三种方法
1. JSON.parse(JSON.stringify(obj))
一种简单且常用的深拷贝方法是利用 JSON 的序列化和反序列化功能。通过将对象或数组转换为 JSON 字符串,然后再将其解析回对象或数组,可以获得一个完全独立于原始数据的拷贝:
const originalObj = { key: 'value', nested: { a: 1, b: 2 } };
const deepCopiedObj = JSON.parse(JSON.stringify(originalObj));
console.log(deepCopiedObj);
这种方法的优点是简单易用,适用于大多数普通的 JSON-safe 对象和数组。然而,它有一些限制,不能处理包含函数、undefined
等特殊值的对象,并且会丢失对象的原型链信息。
2. 使用递归手动实现深拷贝
递归是一种强大的深拷贝方法,可以处理各种复杂的数据结构。通过遍历对象或数组的每个属性,对其进行递归复制,可以实现完全独立的深拷贝:
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
const clone = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
const originalObj = { key: 'value', nested: { a: 1, b: 2 } };
const deepCopiedObj = deepClone(originalObj);
console.log(deepCopiedObj);
这种方法可以处理包含函数、undefined
等特殊值的对象,同时保留对象的原型链。但需要注意,对于循环引用的对象,递归深拷贝可能陷入死循环,因此需要额外的处理来解决这个问题。
3. 使用第三方库(如Lodash)
第三方库通常提供了高效且可靠的深拷贝方法,其中 Lodash 是一个广泛使用的实用工具库,它包含了 cloneDeep
方法:
const _ = require('lodash');
const originalObj = { key: 'value', nested: { a: 1, b: 2 } };
const deepCopiedObj = _.cloneDeep(originalObj);
console.log(deepCopiedObj);
使用第三方库的优点是它们经过广泛测试和优化,通常能够处理各种边界情况,并提供更好的性能。此外,它们还可能包含其他实用的功能,使开发更加便捷。常见的一个第三方库还有UnderScore
总结
深拷贝是确保对象和数组完全独立的重要方法,特别适用于涉及复杂数据结构和嵌套的情况。在选择深拷贝方法时,可以根据具体的需求和数据结构的复杂程度来决定使用哪种方法。 JSON 序列化、递归手动实现以及第三方库都是可行的选择,每种方法都有其适用的场景。在实际应用中,根据项目的具体情况选择最合适的深拷贝方法是明智的选择。
结语
那么我们今天的文章就到此结束啦,如果你感觉文章对你有帮助的话,给作者点个免费的赞吧♥
这个专栏在持续更新更多面试干货中,关注➕收藏 Coding不迷茫
所有文章的源码,给作者的开源git仓库点个收藏吧: gitee.com/cheng-bingw…