在写这篇博客之前,看了很多博客实现的浅拷贝,发现大家实现的方法或多或少都有些不足,今天就把这些坑说一说。
通过逐个遍历对象的属性并复制,来实现浅拷贝,但这种方法有两个弊端:
Symbol 作为属性名,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。
// 浅拷贝函数
function shallowClone(obj) {
const newObj = {};
for (let i in obj) {
newObj[i] = obj[i];
}
return newObj;
}
// 被拷贝对象
const obj1 = {
a: 1,
b: 'hellow world',
c: true,
d: Symbol('symbol'),
[Symbol('e')]: 'e',
}
Object.defineProperty(obj1, 'f', {
value: 'f',
enumerable: false,
}) // 添加不可枚举属性
const obj2 = shallowClone(obj1);
obj2 // {a: 1, b: "hellow world", c: true, d: Symbol(symbol)}
所拷贝的对象并没有以symbol为属性名的属性,也不能拷贝不可遍历的属性,所以这种方法并不是最佳实践。
ES6新增了许多操作对象或者遍历对象的API,下面分别测试下能不能实现完美的浅拷贝
通过和空对象合并来实现浅拷贝:
const obj1 = {
a: 1,
b: 'hellow world',
c: true,
d: Symbol('symbol'),
[Symbol('e')]: 'e',
}
const obj2 = Object.assign({},obj1) // ES6新扩展: Object.assign()
obj2 // {a: 1, b: "hellow world", c: true, d: Symbol(symbol), Symbol(e): "e"}
发现可以实现对以symbol为属性名的属性的拷贝,但是对于不可枚举属性Object.assign()
还是无能为力。 我们在MDN上发现Object.assign()不可以遍历出不可枚举属性:
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
那我们看看更加优雅的解构赋值+rest能不能实现完美的浅克隆通过ES6的解构赋值可以更简单的实现浅拷贝:
const obj1 = {
a: 1,
b: 'hellow world',
c: true,
d: Symbol('symbol'),
[Symbol('e')]: 'e',
}
Object.defineProperty(obj1, 'f', {
value: 'f',
enumerable: false,
});
let { ...obj2 } = obj1
obj2 // {a: 1, b: "hellow world", c: true, d: Symbol(symbol), Symbol(e): "e"}
发现依然不能解决不可遍历属性的问题。
Object.getOwnPropertyDescriptor()
方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
Object.getOwnPropertyDescriptors
方法可以配合Object.create
方法实现浅拷贝:
const shallowClone = (obj) => Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
const obj1 = {
a: 1,
b: 'hellow world',
c: true,
d: Symbol('symbol'),
[Symbol('e')]: 'e',
}
Object.defineProperty(obj1, 'f', {
value: 'f',
enumerable: false,
});
const obj2 = shallowClone(obj1);
obj2 //{a: 1, b: "hellow world", c: true, d: Symbol(symbol), f: "f"}
基本上完美实现了对所有类型属性的拷贝,可以看见即使是浅拷贝,要踩得坑还是很多的。