特性
数据属性
- configurable
- enumerable
- writable
- value
相信看过vue源码,或者实现过深拷贝会熟悉这部分内容。如何读取?
Object.getOwnPropertyDescriptors(obj)
利用这个方法可以获取到对象上的各个数据属性的具体情况。
也可以直接使用 Object.freeze(obj)
冻结,只是冻结一层。
属性遍历
继承
实战:深浅拷贝
浅拷贝
- Object.assign 和 … 拷贝对象
- concat 拷贝数组
深拷贝
JSON.stringify 一招鲜
如果对象里有嵌套。那不行了,还是得深拷贝
首先是JSON.stringify
这样有问题:
- 忽略函数、undefined、symbol
- 拷贝Date会变成字符串
- 不能拷贝不可枚举,不能拷贝对象的原型链
- 拷贝RegExp会变成空对象
- 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null;
- 不能解决循环引用的对象
一般情况下可以干。
lodash deepClone
lodash 的深拷贝函数 https://lodash.com/docs/#cloneDeep
手写1
要考虑:
- 多层嵌套
- 特殊类型
先写一个基础:递归+for…in
function deepClone(obj) {
let cloneObj = {};
for (let key in obj) {
// 如果属性是引用
if (typeof obj[key] === "object") {
// 递归
cloneObj[key] = deepClone(obj[key]);
} else {
// primitvie value最简单
cloneObj[key] = obj[key];
}
}
return cloneObj;
}
这里只解决了一部分问题,但:
- 不可枚举
- symbol
- 循环引用
- 这种方法只是针对普通的引用类型的值做递归复制,而对于 Array、Date、RegExp、Error、Function 这样的引用类型并不能正确地拷贝;
手写2
- 不可枚举属性 和 symbol属性?Reflect.ownKeys
- 如果是Date 和RegExp 生成新的实例返回
- 通过 Object.getOwnPropertyDescriptiors 获取对象定义的特性
- Object.create 创建新对象,继承传入原对象的原型链
- 循环引用?weakMap做hash表,如果真有循环引用,返回WeakMap存储的值
// 忽略null,且是引用类型object或者function
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
const deepClone = function (obj, hash = new WeakMap()) {
// 处理date
if (obj.constructor === Date)
return new Date(obj) // 日期对象直接返回一个新的日期对象
// 处理 RegExp
if (obj.constructor === RegExp)
return new RegExp(obj) //正则对象直接返回一个新的正则对象
// 如果存在相互引用,则从map中取出之前拷贝的结果对象并返回以便形成相互引用关系
if (hash.has(obj)) return hash.get(obj)
// 处理原型链,和不可枚举
let allDesc = Object.getOwnPropertyDescriptors(obj)
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
hash.set(obj, cloneObj)
// 少不了的迭代
for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
}
return cloneObj
}
- Reflect
- Object.getOwnPropertyDescriptiors
- weakMap
map的key可以是任意值,而 WeakMap 的key只能是对象。