引言
在JavaScript的学习过程中,数据的拷贝机制一直都是一个很重要的知识点。前端面试中,深浅拷贝一直都是一个很频繁的问题,下面我们一起全面总结学习下JavaScript的数据拷贝机制,带你一文深入掌握深拷贝与浅拷贝。
浅拷贝(Shallow Copy)
浅拷贝指的是复制对象的第一层属性,如果属性是基本数据类型,则直接复制其值;但如果属性是引用类型(如对象、数组),则复制的是内存地址的引用,而不是实际的值。
手搓浅拷贝
function shallowCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj; // 如果是基本数据类型,直接返回
}
let copy = Array.isArray(obj) ? [] : {}; // 区分数组和对象
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = obj[key]; // 只复制第一层
}
}
return copy;
}
Object.assign()
Object.assign(target, ...sources) 用于将一个或多个源对象(source)的可枚举属性(包括自身的字符串键属性,但不包括继承属性和 Symbol 属性)拷贝到目标对象(target),并返回目标对象。
const objNew = Object.assign({},obj);
Array.prototype.concat()
concat() 方法用合并两个或多个数组,并返回一个新数组,不会改变原数组。
如果 concat() 处理的是引用类型(对象、数组),它会复制引用,而不会真正拷贝对象本身。
const obj = { name: "GGBond" };
const arr1 = [obj];
const arr2 = arr1.concat([{ age: 25 }]);
arr1[0].name = "小猪Passion"; // 修改原对象
console.log(arr2[0].name); // "小猪Passion"(影响到了合并后的数组)
Array.prototype.slice()
slice() 方法用于从一个数组中提取部分元素,并返回一个新数组,不会修改原数组。
const obj = { name: "GGBond" };
const arr = [obj, 2, 3];
const newArr = arr.slice();
newArr[0].name = "小猪Passion";
console.log(arr[0].name); // "小猪Passion"(原数组也受影响)
console.log(newArr[0].name); // "小猪Passion"
扩展运算符 ...
ES6 引入的展开运算符(...)也可以用来实现引用数据类型的浅拷贝。
const obj = { title: "GGBond" };
const arr = [obj, 100, 200];
const newArr = [...arr]; // 使用扩展运算符进行浅拷贝
newArr[0].title = "小猪Passion";
console.log(arr[0].title); // "小猪Passion"(原数组也受影响)
console.log(newArr[0].title); // "小猪Passion"
Object.create()
Object.create(proto, [propertiesObject]) 方法用于创建一个新对象,并指定该对象的原型(proto)。新创建的对象可以继承 proto 的属性和方法。
const obj = { a: 1, b: 2 };
const shallowCopy = Object.create(obj);
console.log(shallowCopy.a); // 1
console.log(shallowCopy.b); // 2
console.log(shallowCopy.hasOwnProperty('a')); // false(因为它是从原型继承的)
注意:
如果你用Object.create()进行浅拷贝,会遇到原型继承的问题:
1.复制的对象属性会变成新对象的原型属性,而不是自身属性。
2.不会真正拷贝对象的值,而是创建了一个继承关系。
深拷贝(Deep Copy)
深拷贝指的是递归拷贝对象的所有层级,即不仅复制第一层属性,还会创建新的子对象或数组,从而使新对象与原对象完全独立,互不影响。
手写深拷贝
function deepClone(value) {
if (typeof value !== 'object' || value === null) {
return value; // 如果是基本数据类型,直接返回
}
let objCopy = Array.isArray(value) ? [] : {}; // 区分数组和对象
for (let key in value) {
if (value.hasOwnProperty(key)) {
objCopy[key] = deepClone(value[key]);
}
}
return objCopy;
}
JSON.parse(JSON.stringify())
一种常见的简便方式是使用 JSON.stringify() 将对象序列化为 JSON 字符串,然后使用 JSON.parse()解析为一个新的对象。
const obj = { name: "小猪Passion", age: 18 };
const newObj = JSON.parse(JSON.stringify(obj));
newObj.name = "GGBond";
console.log(obj.name); // "小猪Passion"(原对象不变)
console.log(newObj.name); // "GGBond"(新对象修改后不影响原对象)
注:
- 无法处理非
JSON结构的引用数据类型;- 当属性值为
undefined时,这些属性会被忽略。
structuredClone()
structuredClone() 是一个现代 JavaScript 内置方法,用于深拷贝对象或数组,能够处理复杂数据类型。
const obj = { name: "小猪Passion", age: 18 };
const newObj = structuredClone(obj);
newObj.name = "GGBond";
console.log(obj.name); // "小猪Passion"(原对象不变)
console.log(newObj.name); // "GGBond"(新对象修改后不影响原对象)
lodash 库
使用lodash的cloneDeep函数进行深拷贝。
// 使用 lodash.cloneDeep() 进行深拷贝
const obj = { name: "小猪Passion", details: { age: 18 } };
const clonedObj = _.cloneDeep(obj);
clonedObj.details.age = 19;
console.log(obj.details.age); // 18 (原对象不变)
console.log(clonedObj.details.age); // 19 (新对象修改后不影响原对象)
小结
在 JavaScript 中,浅拷贝和深拷贝是处理对象和数组时常用的技术。浅拷贝只复制第一层属性,对于引用类型的属性,只复制引用地址,因此修改拷贝后的对象会影响原对象。而深拷贝则递归地复制所有层级的属性,确保新对象与原对象完全独立,互不影响。
上述时小弟的一点点总结,希望能一次积累进步,若存在错误请指出,谢谢大家!!!
学习参考:
1.深拷贝与浅拷贝:详解 JavaScript 中的数据拷贝机制
2.浅拷贝和深拷贝(较为完整的探索)
3.浅拷贝与深拷贝全面解析及实战
文章荐读: