如何克隆一个 javascript ES6类实例

如何使用 ES6克隆 Javascript 类实例。

我对基于 jquery 或 $tended 的解决方案不感兴趣。

我看到过很多关于对象克隆的讨论,这些讨论表明这个问题相当复杂,但是 ES6提供了一个非常简单的解决方案——我将把它放在下面,看看人们是否认为它是令人满意的。

编辑: 有人认为我的问题是一个复制品; 我看到了这个答案,但它是7年前,涉及到非常复杂的答案使用前 ES6 js。我的意思是,我的问题,考虑到 ES6,有一个非常简单的解决方案。

73929 次浏览
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

请注意 对象. 分配的特性: 它执行浅表复制,不复制类方法。

如果你想要一个深度拷贝或者对拷贝有更多的控制,那么就有 加速度加速度克隆函数

这很复杂; 我尝试了很多! 最后,这个一行程序适用于我自定义的 ES6类实例:

let clone = Object.assign(Object.create(Object.getPrototypeOf(orig)), orig)

它避免设置原型,因为 他们说会大大降低代码速度。

它支持符号,但是对于 getter/setter 并不完美,而且不能处理不可枚举的属性(请参见 Object.sign () docs)。此外,克隆基本的内部类(如 Array、 Date、 RegExp、 Map 等)通常似乎需要一些单独的处理。

结论: 这是一团糟。让我们希望有一天会有一个原生的、干净的克隆功能。

极低密度辐射;

// 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 进行扩展,因为当您对代码/组件进行测试时,会出现问题。单元测试框架不会自动假设您的原型扩展。所以这不是个好习惯。 这里有更多关于原型扩展的解释 < a href = “ https://stackoverflow?

要在 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

结果:

LogFullName () ;

结果: Cassio Seffrin

LogFullName () ;

结果: Cassio Seffrin

街道;

结果: ‘ Street B,99’//注意到原来的子对象被更改

注意: 如果实例将闭包作为自己的属性,那么这个方法将不会包装它。(了解更多关于闭包的信息)另外,子对象“ address”不会被克隆。

LogFullName ()

不会工作。克隆不会继承原始的任何原型方法。

LogFullName ()

将工作,因为克隆也将复制其原型。

使用 Object.sign 克隆数组:

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));

使用深度克隆将保留对地址的引用。但是将会丢失 deep Clone Prototype,因此 deep Clone.logFullName ()将不起作用。

第三至 > 第三方图书馆:

另一个选项是使用第三方库,如 loadash 或下划线。 它们将创建一个新对象,并将每个值从原始对象复制到保持其引用在内存中的新对象。

强调: 让 cloneUnderscore = _ (原始) . clone () ;

Loadash 克隆: Var cloneLodash = _. cloneDeep (原创) ;

Loash 或下划线的缺点是需要在项目中包含一些额外的库。然而,它们是很好的选择,也能产生高性能的结果。

您可以使用扩展运算符,例如,如果您想克隆一个名为 Obj 的对象:

let clone = { ...obj};

如果您想要更改或添加任何内容到克隆对象:

let clone = { ...obj, change: "something" };

另一句话:

大多数时候... (适用于 Date,RegExp,Map,String,Number,Array) ,顺便说一句,克隆字符串,Number 有点搞笑。

let clone = new obj.constructor(...[obj].flat())

对于那些没有复制建构子的班级:

let clone = Object.assign(new obj.constructor(...[obj].flat()), obj)

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);

enter image description here

几乎所有的答案我都喜欢。我有这个问题,为了解决这个问题,我会手动定义一个 clone()方法,在它里面,我会从头开始构建整个对象。对我来说,这是有意义的,因为生成的对象将自然地与克隆的对象具有相同的类型。

打字稿示例:

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);
}
}

我喜欢这个解决方案,因为它看起来更“面向对象”

使用与原始对象相同的原型和属性创建对象的副本。

function clone(obj) {
return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj))
}

使用不可枚举属性、 getter、 setter 等。无法克隆内部插槽,许多内置的 javascript 类型都有内部插槽(例如 Array、 Map、 Proxy)

试试这个:

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,所以它还将添加不可枚举的属性。

如果我们有多个彼此扩展的类,克隆每个实例的最佳解决方案是在类定义中定义一个函数来创建该对象的新实例,如:

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)
}
}

现在我可以使用克隆(0函数的对象像:

let p = new ColorPoint(10,10,'red');
let pclone=p.clone();

这样做还不够吗?

Object.assign(new ClassName(), obj)

我用了乐得。

import _ from 'lodash'
class Car {
clone() { return _.cloneDeep(this); }
}

这是对 OP 的更完整的回答,因为到目前为止收到的所有答案都存在问题(并不是说它们不适用于不同的情况和场景,只是它们不是按要求使用 ES6的最简单的通用答案)。为了子孙后代。

Sign ()将只执行一个浅表副本,正如答案 -er 所指出的那样。这实际上是一个大问题,因为 javascript 垃圾收集只有在从原始对象中删除所有引用时才起作用。这意味着任何引用旧对象的克隆都意味着潜在的严重内存泄漏,即使对于很少更改的简单 bool 也是如此。

如果您正在创建可能引用旧实例的新实例(如果对象中存在1个数据子树) ,则使用“ clone ()”方法扩展的类具有与 Object.sign ()相同的垃圾收集问题。这种情况很难单独处理。

使用扩展运算符(“ ...”)也是数组/对象的浅表副本,引用和唯一性方面的问题与上面相同。此外,正如在对答案的响应中所提到的,这无论如何都会丢失原型和类

原型无疑是比较慢的方法,但是我相信 V8在这个方法上有固定的性能问题,所以我不确定在2022年这个问题还存在。

2022年的建议答案: 正确地编写一个深度拷贝脚本来获取所有的类对象数据。当需要克隆一个类对象时,创建一个临时容器并将类对象的深拷贝到临时容器中。编写一个包含所有方法的父类(超类) ,以及对象数据和实例所需的子类。然后,当从扩展的子类调用父类的方法时,将子类的“ this”作为参数传入,并在父类的方法中捕获该参数(例如,我使用了单词“ that”)。最后,当您将对象数据克隆到一个临时对象中时,创建您想要克隆的所有对象的新实例,并用新实例替换对旧实例的任何引用,以确保它不会在内存中逗留。例如,在我的例子中,我正在制作一个康威的《生活的游戏》(Game of Life)的沙包版本。我会有一个名为“ allcells”的数组,然后在每个 requestAnimationFrame (renderFunction)上更新它时,我会将所有单元格深度复制到 temp,运行每个单元格的 update (this)方法,该方法调用父单元格的 update (that)方法,然后创建新的 Cell (temp [0]。X,温度[0]。Y 等) ,并将所有这些打包到一个数组中,在所有更新完成后,我可以用这个数组替换旧的“ allcells”容器。在生活游戏的例子中,如果不在临时容器中执行更新,前一个更新将影响后一个更新在同一时间步骤中的输出,这可能是不可取的。

成交!没有 loash,没有类型脚本,没有 jQuery,只有按要求和通用的 ES6。它看起来很粗糙,但是如果您编写了一个通用递归复制()脚本,您可以很容易地编写一个函数来使用它来创建一个 clone ()函数,如果您想遵循上面概述的步骤并使用下面的示例代码作为参考的话。

function recursiveCopy(arr_obj){
if(typeof arr_obj === "object") {
if ( Array.isArray(arr_obj) ) {
let result = []
// if the current element is an array
arr_obj.forEach( v => { result.push(recursiveCopy(v)) } )
return result
}
else {
// if it's an object by not an array then it’s an object proper { like: “so” }
let result = {}
for (let item in arr_obj) {
result[item] = recursiveCopy(arr_obj[item]) // in case the element is another object/array
}
return result
}
}
// above conditions are skipped if current element is not an object or array, so it just returns itself
else if ( (typeof arr_obj === "number") || (typeof arr_obj === "string") || (typeof arr_obj === "boolean") ) return arr_obj
else if(typeof arr_obj === "function") return console.log("function, skipping the methods, doing these separately")
else return new Error( arr_obj ) // catch-all, likely null arg or something
}


// PARENT FOR METHODS
class CellMethods{
constructor(){
this.numNeighboursSelected = 0
}


// method to change fill or stroke color
changeColor(rgba_str, str_fill_or_stroke, that) {
// DEV: use switch so we can adjust more than just background and border, maybe text too
switch(str_fill_or_stroke) {
case 'stroke':
return that.border = rgba_str
default:      // fill is the default
return that.color = rgba_str
}
}


// method for the cell to draw itself
drawCell(that){
// save existing values
let tmp_fill = c.fillStyle
let tmp_stroke = c.strokeStyle
let tmp_borderwidth = c.lineWidth
let tmp_font = c.font
        

// fill and stroke cells
c.fillStyle = (that.isSelected) ? highlightedcellcolor : that.color
c.strokeStyle = that.border
c.lineWidth = border_width
c.fillRect(that.x, that.y, that.size.width, that.size.height)
c.strokeRect(that.x, that.y, that.size.width+border_width, that.size.height+border_width)
        

// text id labels
c.fillStyle = that.textColor
c.font = `${that.textSize}px Arial`
c.fillText(that.id, that.x+(cellgaps*3), that.y+(that.size.height-(cellgaps*3)))
c.font = tmp_font


// restore canvas stroke and fill
c.fillStyle = tmp_fill
c.strokeStyle = tmp_stroke
c.lineWidth = tmp_borderwidth
}
checkRules(that){
console.log("checking that 'that' works: " + that)
if ((that.leftNeighbour !== undefined) && (that.rightNeighbour !== undefined) && (that.topNeighbour !== undefined) && (that.bottomNeighbour !== undefined) && (that.bottomleft !== undefined) && (that.bottomright !== undefined) && (that.topleft !== undefined) && (that.topright !== undefined)) {
that.numNeighboursSelected = 0
if (that.leftNeighbour.isSelected) that.numNeighboursSelected++
if (that.rightNeighbour.isSelected) that.numNeighboursSelected++
if (that.topNeighbour.isSelected) that.numNeighboursSelected++
if (that.bottomNeighbour.isSelected) that.numNeighboursSelected++
// // if my neighbours are selected
if (that.numNeighboursSelected > 5) that.isSelected = false
}
}
}


// write a class to define structure of each cell
class Cell extends CellMethods{
constructor(id, x, y, selected){
super()
this.id = id
this.x = x
this.y = y
this.size = cellsize
this.color = defaultcolor
this.border = 'rgba(0,0,0,1)'
this.textColor = 'rgba(0,0,0,1)'
this.textSize = cellsize.height/5     // dynamically adjust text size based on the cell's height, since window is usually wider than it is tall
this.isSelected = (selected) ? selected : false
}
changeColor(rgba_str, str_fill_or_stroke){ super.changeColor(rgba_str, str_fill_or_stroke, this)} // THIS becomes THAT
checkRules(){ super.checkRules(this) } // THIS becomes THAT
drawCell(){ super.drawCell(this) } // THIS becomes THAT
}


let [cellsincol, cellsinrow, cellsize, defaultcolor] = [15, 10, 25, 'rgb(0,0,0)'] // for building a grid
// Bundle all the cell objects into an array to pass into a render function whenever we want to draw all the objects which have been created
function buildCellTable(){
let result = []  // initial array to push rows into
for (let col = 0; col < cellsincol; col++) {  // cellsincol aka the row index within the column
let row = []
for (let cellrow = 0; cellrow < cellsinrow; cellrow++) {  // cellsinrow aka the column index
let newid = `col${cellrow}_row${col}` // create string for unique id's based on array indices
row.push( new Cell(newid, cellrow*(cellsize.width),col*(cellsize.height) ))
}
result.push(row)
}
return result
}


// poplate array of all cells, final output is a 2d array
let allcells = buildCellTable()


// create hash table of allcells indexes by cell id's
let cellidhashtable = {}
allcells.forEach( (v,rowindex)=>{
v.forEach( (val, colindex)=>{
cellidhashtable[val.id] = [rowindex, colindex]  // generate hashtable
val.allcellsposition = [rowindex, colindex]     // add cell indexes in allcells to each cell for future reference if already selected
} )
})


// DEMONSTRATION
let originalTable = {'arr': [1,2,3,4,5], 'nested': [['a','b','c'], ['d','e','f']], 'obj': {'nest_obj' : 'object value'}}
let newTable = recursiveCopy(originalTable) // works to copy
let testingDeepCopy = recursiveCopy(newTable)
let testingShallowCopy = {...newTable}  // spread operator does a unique instance, but references nested elements
newTable.arr.pop() // removes an element from a nested array after popping
console.log(testingDeepCopy)   // still has the popped value
console.log(testingShallowCopy)  // popped value is remove even though it was copies before popping


// DEMONSTRATION ANSWER WORKS
let newCell = new Cell("cell_id", 10, 20)
newCell.checkRules()