date: 2022-08-27 18:00:00
深浅拷贝介绍
浅拷贝 : 复制目标对象所有属性的引用指针, 修改复制的一维数据不影响原对象, 修改二维及以上的数据会影响原始对象.
深拷贝 : 完整拷贝对象到另一片内存中, 修改复制的数据不影响原对象.
可能概念上的区别看的有点懵, 看点代码
let person, human;
person = {
name: 'lisnote',
body: {
height: 170,
},
};
console.log('原对象输出:', person);
// 原对象输出: { name: 'lisnote', body: { height: 170 } }
// ---------------浅拷贝--------------------
human = { ...person };
human.name = '不知名名菜';
human.body.height = 175;
console.log('浅拷贝输出:', person);
// 浅拷贝输出: { name: 'lisnote', body: { height: 175 } }
// ---------------深拷贝--------------------
human = JSON.parse(JSON.stringify(person));
human.name = '匿名知名菜';
human.body.height = 180;
console.log('深拷贝输出:', person);
// 拷贝输出: { name: 'lisnote', body: { height: 175 } }
对比三次输出
| 输出类型 | 输出person | 与上次输出变化 |
|---|---|---|
| 初次输出 | { name: 'lisnote', body: { height: 170 } } | |
| 修改浅拷贝后输出 | { name: 'lisnote', body: { height: 175 } } | name无变化 body.height变化 |
| 修改深拷贝后输出 | { name: 'lisnote', body: { height: 175 } } | 无变化 |
可以看到
- 修改浅拷贝对象的一维数据, 原始对象不受影响, 修改浅拷贝的高维数据则原始对象也受影响
- 修改深拷贝对象的数据一维和高维数据, 原始对象都不会受到影响
笔试解
function deepClone(obj) {
let cloneObj = Object.create(Object.getPrototypeOf(obj));
Object.getOwnPropertyNames(obj).forEach((o) => {
cloneObj[o] = obj[o] instanceof Object ? deepClone(obj[o]) : obj[o];
});
return cloneObj;
}
面试
面试的时候面试官让我对我的题目做一个思路分析,
我当时挺懵的, 我做完才注意到考的不是实现而是算法, 我上面还有题浅拷贝用拓展运算符..., 于是在草草的进行了思路分析之后给自己加了点戏,
"其实我这个克隆还有点问题没有解决, 我可以继续跟你分析"
-
遍历问题, getOwnPropertyNames 无法获取到以Symbol为属性的数据, 所以这里是并没有克隆到以Symbol为属性名的值, 若要解决需要使用 getPropertySymbol 再进行一次遍历.
不过所谓优化无止境, 还有一些很特殊的值目前没有遍历, 比如Vue3的 toRaw 方法是通过读取一个__v_raw 属性来获取被 Proxy 的真实对象的, 而该属性并非是以 defineProperty 或是直接赋值进行定义, 他写在 Proxy 的 get 方法一个 if 条件之中, 只有 Proxy 判断读取属性为 __v_raw 时, 这个属性才起作用, 目前我还不能获取到这样极端属性.
// 解决Symbol不遍历 function deepClone(obj) { let cloneObj = Object.create(Object.getPrototypeOf(obj)); [ ...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj), ].forEach((o) => { cloneObj[o] = obj[o] instanceof Object ? deepClone(obj[o]) : obj[o]; }); return cloneObj; } -
值特殊类型问题, 笔试解的那个例子适合克隆json数据, 而像是 Set, WeakMap, Function 等类型是有问题的, 甚至有报错的可能, 例如WeakMap 在 instanceOf Object 都是 true 所以会被递归调用, 但是因为不可遍历而会返回一个空WeakMap, 特殊类型太多, 笔试时间有限, 如有需要往细里写我也可以像Vue3一样用 Object.prototype.toString.call(value).slice(8, -1) 获取属性名后再根据不同的类型进行对应的处理
-
值属性问题, 这个克隆并没有做到将值原原本本的克隆, 在JavaScript中, 值是带有属性的, 比如数组类型的 length 属性不可遍历, 而这个深克隆并没有做到, 正确的做法应该是使用 getOwnPropertyDescriptor 后再用 defineProperty 定义属性.
// 更改为适合针对特殊类型处理的结构 // 添加值时带有属性 function deepClone(obj) { let type = Object.prototype.toString.call(obj).slice(8, -1); if (['Object', 'Array'].indexOf(type) > -1) { let cloneObj = Object.create(Object.getPrototypeOf(obj)); [ ...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj), ].forEach((o) => { Object.defineProperty(cloneObj, o, { ...Object.getOwnPropertyDescriptor(obj, o), value: deepClone(obj[o]), }); }); return cloneObj; } // if (type === 'Set') {} return obj; } -
循环引用问题, 当对象中存在循环引用的时候, 这个深克隆方法会出现超出最大调用堆栈的错误, 如果要解决可以为需要递归遍历的对象建立一个 WeakMap ,当重复引用的时候就使用 WeakMap 中已经克隆过的值.
function deepClone(obj, mapper = new WeakMap()) { let type = Object.prototype.toString.call(obj).slice(8, -1); if (['Object', 'Array'].indexOf(type) > -1) { if (mapper.has(obj)) return mapper.get(obj); let cloneObj = Object.create(Object.getPrototypeOf(obj)); mapper.set(obj, cloneObj); [ ...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj), ].forEach((o) => { Object.defineProperty(cloneObj, o, { ...Object.getOwnPropertyDescriptor(obj, o), value: deepClone(obj[o], mapper), }); }); return cloneObj; } // if (type === 'Set') {} return obj; }
茶余饭后
- 网上有些写的很好的深克隆案例, 写的很详细, 不过了解原理就好, 笔试别照着写, 因为纸张方寸地, 写不下那么多代码, 面试官也不会给你那么多时间, 毕竟面试不是写工具库, 在尽量少的代码展现出尽量多的思考更为合适
- 记录2022.7.5-2022.7.9的某次面试, 笔试时有实现深拷贝, 我的答案以及我的分析, 写完之后瞄了一眼试卷顶部, 原来试卷的名字叫做<<前端算法笔试题>>, 心里凉了一半, 我这写法, API多算法少.
- 面试前我看到他在我给他的简历上写了个B+, 不知道是简历评价还是笔试成绩, 我拍了照片发我哥, 我哥跟我说说不定可有S评价, 我大笑说最高应该是SSR🤣(不会真的有S评价吧?)
原来笔试允许用手机你面试疯狂强调自己看过Vue源码的样子真狼狈, 但你面试通过的样子真的很靓仔