前言
在 JavaScript 中,数据操作是每日必做的 “例行公事”。当我们兴高采烈地对一个对象或数组进行修改时,却常常不小心陷入一个隐形的 “数据陷阱” —— 原数据被意外篡改!这背后,就是深浅拷贝在暗暗发力。
举个常见的栗子,当我们把一个对象简单赋值给另一个变量,以为拥有了一个崭新的副本,实则只是拿到了原对象的 “指向牌”。
var obj = {
a: 1
}
var obj2 = obj;// 读取 obj 地址
obj.a = 3;
console.log(obj2); // 输出:{ a: 3 }
obj
是引用类型,其数据存储在内存的堆中,栈中存储的是堆中对象的地址。
拷贝
- 复刻一个对象,和原对象长的一模一样。
- 深拷贝与浅拷贝的主要区别在于对嵌套对象的处理方式。
注意:引用类型数据的简单赋值不属于拷贝,前面代码例子也不称之为拷贝(仅一个对象)。
浅拷贝
浅拷贝是一种仅复制对象指针或第一层属性值的拷贝方式,新对象与原对象共享引用类型数据的内存地址,修改引用类型数据会影响原对象。
- 只拷贝对象的最外层,原对象的属性值修改会影响原对象。
- 浅拷贝通常只复制对象的基本属性和引用类型地址,而不复制对象的引用类型属性。
- 适用于性能敏感场景,因深拷贝递归操作开销较大;适用于需要快速复制对象且无需独立修改嵌套数据的场景。
let obj = {
name : 'hh',
age : 18,
like : {
a : 'a',
b : 'b',
c : 'c'
} // 读取的是其 like 地址,存放地址-> 堆
}
let newObj = Object.assign({},obj)
newObj.name = 'xx'; // 原始类型 // 不影响原对象
newObj.like.a = 'aa'; // 引用类型 // 获取其地址 // 影响原对象
console.log(newObj); // 输出:{ name: 'xx', age: 18, like: { a: 'aa', b: 'b', c: 'c' } }
console.log(obj); // 输出:{ name: 'hh', age: 18, like: { a: 'aa', b: 'b', c: 'c' } }
编译过程:
实现浅拷贝的方法
Object.create(obj)
let obj = {
a: 1
}
let obj2 = Object.create(obj); // 创建一个新对象,让新对象的隐式原型指向 obj
obj.a = 3
console.log(obj2); // 输出:{} // 显性拥有无属性
console.log(obj2.a); // 输出:3 // 隐式拥有
[].concat(arr)
let arr1 = [1, 2, 3]
let arr2 = [4, 5]
console.log(arr1.concat(arr2)); // 连接两个数组,返回一个新数组,不改变原数组
console.log(arr1); // 输出:[1, 2, 3]
console.log(arr2); // 输出:[4, 5]
let arr3 = [].concat(arr1) // 拷贝
console.log(arr3); // 输出:[4,5]
- 数组解构
[...arr]
let arr = [1, 2, 3]
// const [x, y, z] = arr; // 数组的解构赋值
// console.log(x, y, z); // 输出:1 2 3
console.log(...arr); // 输出:1 2 3 // 扩展运算符(...)-数组的解构语法
let arr2 = [...arr] // 拷贝
console.log(arr2); // 输出:[1,2,3]
arr.slice(0,arr.length)
let arr = ['a', 'b', 'c']
// arr.splice(0, 1); // 从第几下标开始删除,删除几个 // 会影响原数组
// arr.splice(1, 0, 'b'); // 从第几下标开始加值
// arr.slice(0, 2); // 从第几下标开始截取,截取几个 // 左闭右开 // 不会影响原数组
let arr1 = arr.slice(0, arr.length) ; // 拷贝
console.log(arr1); // 输出:['a', 'b', 'c']
Object.assign({}, obj)
let obj = {
name : 'xx',
age : 18
}
let girl = {
nickname : 'hh'
}
// let newObj = Object.assign(obj,girl) // 该方法挂在该构造函数上 // 原对象受影响
let newObj = Object.assign({},obj,girl) // 拷贝 // 原对象不受影响
console.log(obj); // 输出:{name: 'xx', age: 18}
console.log(newObj); // 输出:{name: 'xx', age: 18, nickname: 'hh'}
arr.toReversed().reverse()
let arr = [1,2,3]
// let newArr1 = arr.reverse() // 反转数组 // 原数组受影响
// let newArr = arr.toReversed() // 反转数组 // 原数组不受影响
// console.log(newArr); // 输出:[3,2,1]
let newArr2 = arr.toReversed().reverse() // 拷贝 // 原数组不受影响
console.log(newArr2); // 输出:[1,2,3]
手动实现浅拷贝
let obj = {
name : 'hh',
age : 18,
like : {
a : 'a',
b : 'b',
c : 'c'
}
}
// 创建对象,遍历赋值
function shallowCopy(obj) {
if (typeof obj !== 'object' || obj === null) { //判断 obj 的类型,如果是原始类型,直接返回
return obj;
}
let newObj = {} // 创建对象
for (let key in obj) { // 可获取隐式原型属性
if(obj.hasOwnProperty(key)){ // 判断 key 是否 obj 显示拥有的
newObj[key] = obj[key]; // 不能用 . 表示增加新属性
}
} // 遍历对象
return newObj; // 返回对象
}
let newObj1 = shallowCopy(obj);
console.log(newObj1);
深拷贝
深拷贝是指在js中创建一个新的对象,并递归地复制原始对象中的所有层级的属性(包括子对象),使得新对象与原始对象完全独立。
- 层层拷贝,新对象不受原对象的影响。拷贝对象的所有属性,原对象的属性值修改不会影响原对象。
- 适用于确保对象独立性、不变性及简化并发编程,对象含引用类型、需序列化等情况。
实现深拷贝的方法
JSON.parse(JSON.stringify(obj))
- 无法识别
bigint
类型,无法处理undefined
,symbol
,function
- 无法处理循环引用
let obj = {
name : 'hh',
age : 18,
like : {
a : 'a',
b : 'b',
c : 'c'
} ,
a : undefined,
b : null,
c : function(){},
d : Symbol('123'),
e : {}
}
// let newObj = JSON.stringify(obj); // 转字符串
let newObj = JSON.parse(JSON.stringify(obj)); // 转回对象 // 深拷贝
obj.like.a = 'aa';
console.log(newObj); // 输出:{ name: 'hh',age: 18,like: { a: 'a', b: 'b', c: 'c' },b: null,e: {} }
structuredClone()
- 无法识别
symbol
,function
let obj = {
name : 'hh',
age : 18,
like : {
a : 'a',
b : 'b',
c : 'c'
} ,
a : undefined,
b : null,
e : {}
}
let newObj = structuredClone(obj)// 该方法挂载在 Window 全局作用域
console.log(newObj);// 输出:{ name: 'hh',age: 18,like: { a: 'a', b: 'b', c: 'c' },a:undefined,b: null,e: {} }
手动实现深拷贝
递归深拷贝是指通过递归遍历对象所有层级属性,创建完全独立副本。
let obj = {
name : 'hh',
age : 18,
like : {
a : 'a',
b : 'b',
c : 'c'
} ,
a : undefined,
b : null,
e : {}
}
function deepCopy(obj){
if (typeof obj !== 'object' || obj === null) { //判断 obj 的类型,如果是原始类型,直接返回
return obj;
}
let newObj = {}
for(let key in obj){
if(obj.hasOwnProperty(key)){
//先判断 obj[key] 的类型,如果是原始类型,直接赋值,如果是引用类型
if(typeof obj[key] === 'object' && obj[key] !== null){
newObj[key] = deepCopy(obj[key]); // 递归
}
else{
newObj[key] = obj[key];
}
}
}
return newObj;
}
let newObj1 = deepCopy(obj);
obj.like.a = 'aa';
console.log(newObj1); // 输出:{ name: 'hh',age: 18,like: { a: 'a', b: 'b', c: 'c' },a: undefined,b: null,e: {} }