如何使用 ES6 克隆 Javascript 类实例。
我对基于 jquery 或 $extend 的解决方案不感兴趣。
我已经看到关于对象克隆的相当古老的讨论,这些讨论表明问题非常复杂,但是使用 ES6 提供了一个非常简单的解决方案 - 我将把它放在下面,看看人们是否认为它令人满意。
编辑:有人建议我的问题是重复的;我看到了那个答案,但它已经有 7 年的历史了,并且涉及使用 ES6 之前的 js 的非常复杂的答案。我建议我的问题(允许使用 ES6)有一个非常简单的解决方案。
如何使用 ES6 克隆 Javascript 类实例。
我对基于 jquery 或 $extend 的解决方案不感兴趣。
我已经看到关于对象克隆的相当古老的讨论,这些讨论表明问题非常复杂,但是使用 ES6 提供了一个非常简单的解决方案 - 我将把它放在下面,看看人们是否认为它令人满意。
编辑:有人建议我的问题是重复的;我看到了那个答案,但它已经有 7 年的历史了,并且涉及使用 ES6 之前的 js 的非常复杂的答案。我建议我的问题(允许使用 ES6)有一个非常简单的解决方案。
这很复杂; 我试了很多!最后,这个单行代码适用于我的自定义 ES6 类实例:
let clone = Object.assign(Object.create(Object.getPrototypeOf(orig)), orig)
它避免设置原型,因为他们说它会大大减慢代码的速度。
它支持符号,但对于 getter/setter 并不完美,并且不适用于不可枚举的属性(请参阅Object.assign() 文档)。此外,可悲的是,克隆基本的内部类(如 Array、Date、RegExp、Map 等)似乎经常需要一些单独的处理。
结论:一团糟。让我们希望有一天会有一个原生的和干净的克隆功能。
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );
注意Object.assign的特点:它做一个浅拷贝,不拷贝类方法。
如果你想要一个深拷贝或对拷贝的更多控制,那么有lodash clone 函数。
我喜欢几乎所有的答案。我遇到了这个问题,为了解决它,我会通过定义一个clone()
方法来手动完成它,并在其中从头开始构建整个对象。对我来说,这是有道理的,因为结果对象自然与克隆对象具有相同的类型。
typescript示例:
export default class ClassName {
private name: string;
private anotherVariable: string;
constructor(name: string, anotherVariable: string) {
this.name = name;
this.anotherVariable = anotherVariable;
}
public clone(): ClassName {
return new ClassName(this.name, this.anotherVariable);
}
}
我喜欢这个解决方案,因为它看起来更“面向对象”
TLDR;
// Use this approach
//Method 1 - clone will inherit the prototype methods of the original.
let cloneWithPrototype = Object.assign(Object.create(Object.getPrototypeOf(original)), original);
在 Javascript 中,不建议对 Prototype 进行扩展,这会导致在对代码/组件进行测试时出现问题。单元测试框架不会自动假设您的原型扩展。所以这不是一个好习惯。这里有更多关于原型扩展的解释为什么扩展原生对象是一种不好的做法?
要在 JavaScript 中克隆对象,没有一种简单或直接的方法。这是使用“浅拷贝”的第一个实例:
1 -> 浅克隆:
class Employee {
constructor(first, last, street) {
this.firstName = first;
this.lastName = last;
this.address = { street: street };
}
logFullName() {
console.log(this.firstName + ' ' + this.lastName);
}
}
let original = new Employee('Cassio', 'Seffrin', 'Street A, 23');
//Method 1 - clone will inherit the prototype methods of the original.
let cloneWithPrototype = Object.assign(Object.create(Object.getPrototypeOf(original)), original);
//Method 2 - object.assing() will not clone the Prototype.
let cloneWithoutPrototype = Object.assign({},original);
//Method 3 - the same of object assign but shorter syntax using "spread operator"
let clone3 = { ...original };
//tests
cloneWithoutPrototype.firstName = 'John';
cloneWithoutPrototype.address.street = 'Street B, 99'; //will not be cloned
结果:
original.logFullName();
结果:卡西奥·塞弗林
cloneWithPrototype.logFullName();
结果:卡西奥·塞弗林
original.address.street;
result: 'Street B, 99' // 注意原来的子对象被改变了
注意:如果实例具有闭包作为自己的属性,则此方法将不会包装它。(阅读更多关于闭包的信息)此外,子对象“地址”不会被克隆。
cloneWithoutPrototype.logFullName()
不管用。克隆不会继承原始的任何原型方法。
cloneWithPrototype.logFullName()
会起作用,因为克隆也会复制它的原型。
使用 Object.assign 克隆数组:
let cloneArr = array.map((a) => Object.assign({}, a));
使用 ECMAScript 传播语法克隆数组:
let cloneArrSpread = array.map((a) => ({ ...a }));
2 -> 深度克隆:
要归档一个全新的对象引用,我们可以使用 JSON.stringify() 将原始对象解析为字符串,然后将其解析回 JSON.parse()。
let deepClone = JSON.parse(JSON.stringify(original));
使用深度克隆,将保留对地址的引用。然而,deepClone 原型将会丢失,因此 deepClone.logFullName() 将不起作用。
3 -> 第 3 方库:
另一种选择是使用第 3 方库,如 loadash 或下划线。他们将创建一个新对象并将每个值从原始对象复制到新对象,并将其引用保留在内存中。
下划线:让 cloneUnderscore = _(original).clone();
Loadash 克隆:var cloneLodash = _.cloneDeep(original);
lodash 或下划线的缺点是需要在项目中包含一些额外的库。然而,它们是不错的选择,也能产生高性能的结果。
使用与原始对象相同的原型和相同的属性创建对象的副本。
function clone(obj) {
return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj))
}
适用于不可枚举的属性、getter、setter 等。无法克隆许多内置 javascript 类型(例如数组、映射、代理)所具有的内部插槽