深拷贝和浅拷贝:
-
浅拷贝:只复制指向对象的指针,而不复制对象本身,新旧对象共享一块内存;
对象只会被克隆最外部的一层,至于更深层的对象,则依然是通过引用指向同一块堆内存。 -
深拷贝:复制并创建一个一模一样的对象,不共享内存,修改新对象,旧对象保持不变。
-
如何区分深拷贝与浅拷贝
简单点来说:
就是假设B复制了A,当修改A时,看B是否会发生变化,
如果B也跟着变了,说明这是浅拷贝,修改一个数据会影响另外一个数据。
如果B没变,那就是深拷贝。
浅拷贝拷贝一层,深拷贝拷贝多层。
实现浅拷贝的方法:
- Object.assign(target, source),当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。
Object.assign(target, ...sources) 【target:目标对象】,【souce:源对象(可多个)】
如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖;
后面的源对象的属性将类似地覆盖前面的源对象的属性。
使用例子:
const object1 = {
a: 1,
b: 2,
c: 3
}
const object2 = Object.assign({c: 4, d: 5}, object1)
console.log(object2.c, object2.d)
console.log(object1) // { a: 1, b: 2, c: 3 }
console.log(object2) // { c: 3, d: 5, a: 1, b: 2 }
新增编辑弹窗:
aaa:默认值
mounted:this.form = Object.assign({ aaa: 1 }, this.editData)
-
es6对象扩展运算符 let obj2 = { ...obj }
-
Object.create()
let newObj = Object.create(obj)
原对象被赋值到 newObj 的 proto 上去了 -
循环
function shallowClone(o) {
const obj = {};
for (let i in o) {
obj[i] = o[i];
}
return obj;
}
5. 解构赋值
- 基本数据类型:直接复制值
说明:基本类型(number、string、boolean 等)直接复制值,新旧变量互不影响。
const obj = { a: 1, b: 'hello' };
const { a, b } = obj;
a = 2; // 修改解构后的变量
console.log(obj.a); // 1(原对象未受影响)
- 引用数据类型:复制引用
说明:引用类型(对象、数组等)复制的是内存地址的引用,修改解构后的变量会直接影响原对象。
const obj = {
details: { name: 'Alice', hobbies: ['reading'] }
};
const { details } = obj;
details.name = 'Bob'; // 修改解构后的对象属性
details.hobbies.push('gaming');// 修改解构后的数组
console.log(obj.details.name); // 'Bob'(原对象被修改)
console.log(obj.details.hobbies); // ['reading', 'gaming'](原数组被修改)
实现深拷贝的方法:
1、JSON.parse(JSON.stringify(obj)) (弊端:正则函数、日期会有问题,变成空对象或字符串;无法解决循环引用的问题,可能导致无限递归,出现系统栈溢出)
2、使用递归的方式
function deepClone(obj) {
// 进行深拷贝的不能为空,并且是对象
if (!obj || typeof obj !== 'object') return
//判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝
let newObj = Array.isArray(obj) ? [] : {}
for (let key in obj) {
// Object.hasOwnProperty判断对象是否有某个特定的属性,过滤原型属性
// 因为:原型属性通常为共享方法(如Object.prototype.toString)复制原型属性会导致冗余数据
// 这是深拷贝实现中非常重要的边界控制条件
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ?
deepClone(obj[key]) : obj[key]
}
}
return newObj
}
3、在生产环境中使用函数库lodash-es的cloneDeep()
import { debounce, throttle, cloneDeep } from 'lodash-es';
4、通过jQuery的$.extend()实现深拷贝,当extend内的第一个参数为true时,实现的是深拷贝,false是浅拷贝。
5、Object.assign(target, source),当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。
手写实现
/**
* 深拷贝函数(处理循环引用和特殊对象类型)
* @param {any} value - 需要拷贝的值
* @param {WeakMap} hash - 弱映射表用于存储已拷贝对象(解决循环引用)
* @returns {any} 深拷贝后的新对象
*/
function deepClone(value, hash = new WeakMap()) {
// value - 需要拷贝的值;hash - 弱映射表用于存储已拷贝对象(解决循环引用)
// 选择WeakMap而不是Map,因为WeakMap的键是弱引用,不会造成内存泄漏,适合这种临时存储的场景
// 基础类型直接返回(包括null、undefined、string、number、boolean、symbol等)
if (value === null || typeof value !== 'object') {
return value;
}
// 处理 Date 类型(创建新的Date实例)
if (value instanceof Date) {
return new Date(value); // 拷贝日期对象的时间戳
}
// 处理 RegExp 类型(创建新的正则表达式)
if (value instanceof RegExp) {
return new RegExp(value); // 保留源正则的表达式和标志
}
// 处理数组类型(递归拷贝每个元素)
if (Array.isArray(value)) {
const arrCopy = [];
for (let item of value) {
// 递归拷贝数组元素,并传递hash表
arrCopy.push(deepClone(item, hash));
}
return arrCopy;
}
// 检查循环引用(如果已经拷贝过则直接返回已存储的副本)
if (hash.has(value)) {
return hash.get(value); // 避免无限递归的关键
}
// 创建空对象副本,并存入hash表(必须在递归前存入)
const objCopy = {};
hash.set(value, objCopy);
// 遍历对象自身可枚举属性
for (let key in value) {
// 检查对象属性是否为该对象自身拥有的属性(而非从原型链继承的属性)过滤原型属性,不拷贝原型链上的属性
// 因为:原型属性通常为共享方法(如Object.prototype.toString)复制原型属性会导致冗余数据
// 这是深拷贝实现中非常重要的边界控制条件
if (value.hasOwnProperty(key)) {
// 递归拷贝每个属性值
objCopy[key] = deepClone(value[key], hash);
}
}
return objCopy;
}
// 执行流程说明:
// 1. 基础类型直接返回
// 2. 特殊对象类型(Date / RegExp) 创建新实例
// 3. 数组类型递归拷贝元素
// 4. 普通对象处理:
// a.检查循环引用,有则直接返回副本
// b.创建空对象,存入hash表
// c.递归拷贝每个自身属性
// d.返回拷贝后的新对象
深拷贝怎么解决循环引用的问题
参考解决方式一:使用WeakMap:
解决循环引用问题,我们可以开辟一个存储空间,来存储当前对象和拷贝对象的对应关系,
这个存储空间,需要可以存储key-value形式的数据,且key是一个引用类型。
我们可以选WeakMap这种数据结构:
检查WeakMap中有无克隆过的对象,有,直接返回,没有,将当前对象作为key,克隆对象作为value进行存储,继续克隆。
参考解决方式二: 可以用Set,发现相同的对象直接赋值,也可用Map
解决方法应该是 用一个 Map 来存储引用类型,然后每次遇到引用属性时,就用 has 查看是否已经有了这个引用。
深拷贝如何拷贝函数
toString()方法
