如何使用ES6克隆JavaScript类实例。
我不感兴趣使用基于jquery或$extend的解决方案。
我看到了相当古老的关于对象克隆的讨论,它表明问题相当复杂,但是在ES6中可以提供一个非常简单的解决方案-我将在下面给出并查看人们是否认为它是令人满意的。
编辑:有人建议我的问题是重复的;我看到了那个答案,但它已经有7年历史了,并且使用了使用ES6之前非常复杂的答案。我认为我的问题,允许使用ES6,有一个极大简化的解决方案。
如何使用ES6克隆JavaScript类实例。
我不感兴趣使用基于jquery或$extend的解决方案。
我看到了相当古老的关于对象克隆的讨论,它表明问题相当复杂,但是在ES6中可以提供一个非常简单的解决方案-我将在下面给出并查看人们是否认为它是令人满意的。
编辑:有人建议我的问题是重复的;我看到了那个答案,但它已经有7年历史了,并且使用了使用ES6之前非常复杂的答案。我认为我的问题,允许使用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克隆函数。
Object.create
可以创建指定原型的新对象,那为什么不直接使用const clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah)
呢?此外,类方法也会被复制。 - barbatusBlah.prototype != instanceOfBlah
。你应该使用 Object.getPrototypeOf(instanceOfBlah)
。 - Bergi我喜欢几乎所有的答案。我曾经遇到过这个问题,为了解决它,我会手动定义一个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);
}
}
我喜欢这个解决方案,因为它看起来更像“面向对象”
public static clone(instance: MyClass): MyClass)
,它具有特定处理克隆的相同想法,只是将其外部化到实例之外。 - VLAZ简而言之;
// Use this approach
//Method 1 - clone will inherit the prototype methods of the original.
let cloneWithPrototype = Object.assign(Object.create(Object.getPrototypeOf(original)), original);
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();
结果:Cassio Seffrin
cloneWithPrototype.logFullName();
结果:Cassio Seffrin
original.address.street;
结果:'Street B, 99' // 注意原始子对象已更改
注意:如果实例具有闭包作为自己的属性,则此方法不会将其包装。(了解有关闭包的更多信息) 而且,子对象“address”将不会被克隆。
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 ->第三方库:
另一种选择是使用第三方库,如loadash或underscore。 它们将创建一个新对象,并将原始对象中的每个值复制到新对象中,在内存中保留其引用。
Underscore: let cloneUnderscore = _(original).clone();
Loadash clone: var cloneLodash = _.cloneDeep(original);
lodash或underscore的缺点在于需要在项目中包含一些额外的库。然而,它们是不错的选择,也能产生高性能的结果。
{}
时,克隆对象将不继承原始对象的任何原型方法。clone.logFullName()
将完全无法运作。你之前使用的 Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal)
是可以的,为什么要改变它呢? - BergiObject.assign({},original)
一样,它也不起作用。 - Bergifunction clone(obj) {
return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj))
}
可以处理非枚举属性、getter、setter等。但无法克隆内部插槽,许多内置的JavaScript类型都有这些插槽(例如Array、Map、Proxy)。
如果我们有多个继承自彼此的类,克隆每个实例的最佳解决方案是在其类定义中定义一个函数,用于创建该对象的新实例,如下所示:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
clone() {
return new Point(this.x, this.y);
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
clone() {
return new ColorPoint(
this.x, this.y, this.color.clone()); // (A)
}
}
let p = new ColorPoint(10,10,'red');
let pclone=p.clone();
试试这个:
function copy(obj) {
//Edge case
if(obj == null || typeof obj !== "object") { return obj; }
var result = {};
var keys_ = Object.getOwnPropertyNames(obj);
for(var i = 0; i < keys_.length; i++) {
var key = keys_[i], value = copy(obj[key]);
result[key] = value;
}
Object.setPrototypeOf(result, obj.__proto__);
return result;
}
//test
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
};
var myPoint = new Point(0, 1);
var copiedPoint = copy(myPoint);
console.log(
copiedPoint,
copiedPoint instanceof Point,
copiedPoint === myPoint
);
Object.getOwnPropertyNames
, it will also add non-enumerable properties.
class A {
constructor() {
this.x = 1;
}
y() {
return 1;
}
}
const a = new A();
const output = Object.getOwnPropertyNames(Object.getPrototypeOf(a))
.concat(Object.getOwnPropertyNames(a))
.reduce((accumulator, currentValue, currentIndex, array) => {
accumulator[currentValue] = a[currentValue];
return accumulator;
}, {});
console.log(output);
output instanceof A
是 false
。2. 克隆只在原型链上向上一级,如果有一个 class B extends A { b() { return 2; }}
和 class C extends B { c() { return 3; }}
,那么“克隆”一个 C
实例最终只会复制 b()
和 c()
,但不会复制 A
的属性 (y
)。属性 x
之所以会被复制,是因为它在构造函数中直接设置在实例上。 - VLAZ另一个一行代码:
大多数情况下...(适用于Date、RegExp、Map、String、Number、Array),顺便说一下,克隆字符串和数字有点有趣。
let clone = new obj.constructor(...[obj].flat())
对于那些没有复制构造函数的类:
let clone = Object.assign(new obj.constructor(...[obj].flat()), obj)
fn(...[obj].flat())
=== fn(obj)
没有真正的理由去使用额外的 1. 数组,2. 将其扁平化为只有一个成员的数组。3. 将该单个成员展开为一个参数。即使这样,这仅适用于具有复制构造函数的类型。第二个版本不一定适用于没有复制构造函数的类 - 它甚至可能导致错误,考虑 constructor(a, b) { this.c = a + b }
,它通常期望数字但对于 a
得到了自身的实例,对于 b
得到了 undefined
。 - VLAZ这样做不够吗?
Object.assign(new ClassName(), obj)
this
将指向旧实例,然后还有私有字段...很多陷阱。 - Thomas