数组和对象深浅拷贝
对象浅拷贝
方法一:使用 Object.assign()
Object.assign()
方法用于将一个或多个源对象的所有可枚举属性复制到目标对象。它会返回目标对象。
const originalObj = {
name: 'John',
age: 30,
hobbies: ['reading', 'running']
};
const shallowCopy = Object.assign({}, originalObj);
console.log(shallowCopy);
// 修改浅拷贝对象的基本类型属性
shallowCopy.name = 'Jane';
console.log(originalObj.name); // John
console.log(shallowCopy.name); // Jane
// 修改浅拷贝对象的引用类型属性
shallowCopy.hobbies.push('swimming');
console.log(originalObj.hobbies); // ['reading', 'running', 'swimming']
console.log(shallowCopy.hobbies); // ['reading', 'running', 'swimming']
方法二:使用扩展运算符(...
)
扩展运算符可以将一个对象或数组展开。
const originalObj = {
name: 'John',
age: 30,
hobbies: ['reading', 'running']
};
const shallowCopy = {...originalObj };
console.log(shallowCopy);
// 修改浅拷贝对象的基本类型属性
shallowCopy.name = 'Jane';
console.log(originalObj.name); // John
console.log(shallowCopy.name); // Jane
// 修改浅拷贝对象的引用类型属性
shallowCopy.hobbies.push('swimming');
console.log(originalObj.hobbies); // ['reading', 'running', 'swimming']
console.log(shallowCopy.hobbies); // ['reading', 'running', 'swimming']
方法三:使用Object.create()
let a = {
name: '艾总',
like: {
n: 'running'
}
}
let b = Object.create(a)
a.name = '黎总'
console.log(a);
console.log(b);
console.log(b.name);
//{ name: '黎总', like: { n: 'running' } }
//{}
//黎总
数组浅拷贝
方法一:[].concat(arr)
let arr = [1, 2, 3, { a: 10 }]
let newArr = [1, 2].concat(arr)
arr[3].a = 100
console.log(arr);
console.log(newArr);
//[ 1, 2, 3, { a: 100 } ]
//[ 1, 2, 1, 2, 3, { a: 100 } ]
方法二:arr.slice(0)
arr = [1, 2, 3, 4, 5, { age: 18 }]
const arr1 = arr.slice(0)
arr[5].age = 20
console.log(arr);
console.log(arr1);
//[ 1, 2, 3, 4, 5, { age: 20 } ]
//[ 1, 2, 3, 4, 5, { age: 20 } ]
方法三:arr.toReversed().reverse()
arr = [1, 2, 3, 4, 5, { age: 18 }]
const arr1 = arr.toReversed().reverse()
arr[5].age = 20
console.log(arr);
console.log(arr1);
//[ 1, 2, 3, 4, 5, { age: 20 } ]
//[ 1, 2, 3, 4, 5, { age: 20 } ]
方法四:使用扩展运算符(...
)
扩展运算符可以将一个对象或数组展开。
对象深拷贝
方法一:使用 JSON.parse(JSON.stringify())
这是一种简单的深拷贝方法,但有一些局限性,比如不能处理函数、Symbol
类型的属性和 Date
对象等。
const originalObj = {
name: 'John',
age: 30,
hobbies: ['reading', 'running']
};
const deepCopy = JSON.parse(JSON.stringify(originalObj));
console.log(deepCopy);
// 修改深拷贝对象的基本类型属性
deepCopy.name = 'Jane';
console.log(originalObj.name); // John
console.log(deepCopy.name); // Jane
// 修改深拷贝对象的引用类型属性
deepCopy.hobbies.push('swimming');
console.log(originalObj.hobbies); // ['reading', 'running']
console.log(deepCopy.hobbies); // ['reading', 'running', 'swimming']
方法二:自定义递归函数
通过递归遍历对象的所有属性,复制每个属性的值。
使用typeof检测引用数据类型(如对象、数组、函数等)时,除了函数会返回 'function'
外,其他的引用数据类型都会返回 'object'
。
function deepClone(obj) {
// 使用typeof检测引用数据类型(如对象、数组、函数等)时,
// 除了函数会返回 'function' 外,其他的引用数据类型都会返回 'object'。
//typeof 数组 会返回'object'
//typeof 无法细致区分不同类型的引用数据,所以统一将数组归类为 'object'。
if (typeof obj !== 'object' || obj === null) {
//后续调用deepClone方法时如果是基本数据类型,会直接返回!!!
return obj
}
let clone;
if (Array.isArray(obj)) {
clone = [];
for (let i = 0; i < obj.length; i++) {
clone[i] = deepClone(obj[i]);
}
} else {
//进入这里代表obj是一个对象,对对象进行深拷贝时必须确保拷贝的属性是当前对象自身的属性
clone = {}
// 使用 for...in 循环进行遍历时,会遍历到对象原型链上的可枚举属性。
for (let key in obj) {
//所以使用obj.hasOwnProperty(key)进行确认
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key])
}
}
}
return clone;
}
const originalObj = {
name: 'John',
age: 30,
hobbies: ['reading', 'running']
};
const deepCopy = deepClone(originalObj);
console.log(deepCopy);
// 修改深拷贝对象的基本类型属性
deepCopy.name = 'Jane';
console.log(originalObj.name); // John
console.log(deepCopy.name); // Jane
// 修改深拷贝对象的引用类型属性
deepCopy.hobbies.push('swimming');
console.log(originalObj.hobbies); // ['reading', 'running']
console.log(deepCopy.hobbies); // ['reading', 'running', 'swimming']
注意事项
- 虽然
for...in
会遍历原型链上的可枚举属性,但现代 JavaScript 中很多内置对象的原型属性是不可枚举的,比如Object.prototype
上的方法(toString
、hasOwnProperty
等),所以for...in
通常不会遍历到这些方法。 for...in
遍历属性的顺序是不确定的,不同的 JavaScript 引擎可能会有不同的实现,所以不要依赖for...in
的遍历顺序。