如何克隆 javascript ES6 class 实例
How to clone a javascript ES6 class instance
如何使用 ES6 克隆 Javascript class 实例。
我对基于 jquery 或 $extend 的解决方案不感兴趣。
我已经看到关于对象克隆的很早的讨论表明这个问题很复杂,但是对于 ES6 来说,一个非常简单的解决方案本身就出现了——我将把它放在下面,看看人们是否认为它令人满意。
编辑:有人认为我的问题是重复的;我看到了那个答案,但它已有 7 年历史,涉及使用 ES6 之前的 js 的非常复杂的答案。我建议我的问题(允许 ES6)有一个非常简单的解决方案。
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );
注意Object.assign的特点:浅拷贝,不拷贝class方法
如果你想要深拷贝或对拷贝有更多的控制,那么有lodash clone functions。
很复杂;我试了很多!最后,这个单行代码适用于我的自定义 ES6 class 个实例:
let clone = Object.assign(Object.create(Object.getPrototypeOf(orig)), orig)
它避免设置原型,因为 they say 它会大大降低代码速度。
它支持符号,但不适合 getters/setters,并且不能使用不可枚举的属性(参见 Object.assign() docs)。此外,克隆基本的内部 classes(如 Array、Date、RegExp、Map 等)遗憾的是似乎经常需要一些单独的处理。
结论:一团糟。让我们希望有一天会有一个本机和干净的克隆功能。
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 中不建议对原型进行扩展,这会导致在 code/components 上进行测试时出现问题。单元测试框架不会自动假设你的原型扩展。所以这不是一个好习惯。
这里有更多关于原型扩展的解释Why is extending native objects a bad practice?
要克隆 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();
result: Cassio Seffrin
cloneWithPrototype.logFullName();
result: Cassio Seffrin
original.address.street;
result: 'Street B, 99' // notice that original sub object was changed
注意:如果实例将闭包作为自己的属性,则此方法不会包装它。 (read more about closures)另外,子对象“地址”不会被克隆。
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 或 underscore。
他们将创建一个新对象并将每个值从原始对象复制到新对象,并在内存中保留其引用。
下划线:
让 cloneUnderscore = _(original).clone();
Loadash 克隆:
var cloneLodash = _.cloneDeep(原版);
lodash 或 underscore 的缺点是需要在项目中包含一些额外的库。然而,它们是不错的选择,而且还能产生高性能结果。
您可以使用扩展运算符,例如,如果您想克隆一个名为 Obj 的对象:
let clone = { ...obj};
如果您想更改克隆对象或向其添加任何内容:
let clone = { ...obj, change: "something" };
另一种班轮:
大多数时候...(适用于 Date、RegExp、Map、String、Number、Array),顺便说一句,克隆字符串、数字有点搞笑。
let clone = new obj.constructor(...[obj].flat())
对于那些 class 没有复制构造函数的人:
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);
几乎所有答案我都喜欢。我遇到了这个问题,为了解决它,我会通过定义一个 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 相互扩展,克隆每个实例的最佳解决方案是定义一个函数来在其 class 定义中创建该对象的新实例,例如:
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)
我用的是 lodash。
import _ from 'lodash'
class Car {
clone() { return _.cloneDeep(this); }
}
这是对 OP 的更完整的回答,因为到目前为止收到的所有回答都存在问题(并不是说它们有时不适用于不同的案例和场景,只是它们不是根据要求仅使用 ES6 的最简单的通用答案)。为了子孙后代。
Object.assign() 只会进行浅拷贝,如 answer-er 所述。这实际上是一个大问题,因为 javascript 垃圾收集仅在所有引用都从原始对象中删除时才有效。这意味着任何引用旧对象的克隆,即使是很少更改的简单布尔值,也意味着潜在的严重内存泄漏。
Class 使用“clone()”方法扩展具有与 Object.assign() 相同的垃圾收集问题,如果您正在创建可能引用旧实例的新实例,即使 1 sub-tree 对象中存在数据。这很难单独管理。
使用扩展运算符(“...”)也是arrays/objects的浅拷贝,与上面的引用和唯一性问题相同。此外,正如对答案的回应中也提到的那样,这会丢失原型并且 class 无论如何
原型绝对是较慢的方法,但我相信 V8 已经解决了这种方法的性能问题,所以我不确定它在 2022 年是否还会成为问题。
2022 年的建议答案:正确编写深层复制脚本以获取所有 class 对象数据。当想要克隆一个 class 对象时,创建一个临时容器并将 class 对象深拷贝到临时容器中。写一个包含所有方法的父 class (superclass),以及你想要的对象数据和实例的子class。然后当从扩展 subclass 调用父方法时,将 subclass 的“this”作为参数传入,并在父方法中捕获该参数(我使用“that”这个词,例如)。最后,当您将对象数据克隆到临时对象中时,为您要克隆的所有对象创建新实例,并将对旧实例的任何引用替换为新实例,以确保它不会在内存中逗留。例如,在我的示例中,我正在制作康威生命游戏的 hacky 版本。我会有一个名为“allcells”的数组,然后在每个 requestAnimationFrame(renderFunction) 上更新它时,我会将 allcells 深度复制到 temp,运行 每个单元格的 update(this) 方法调用父级的 update(that) 方法,然后创建新的 Cell(temp[0].x, temp[0].y, etc) 并将所有这些打包到一个数组中,我可以在完成所有更新后用它替换旧的“allcells”容器。在生命游戏示例中,如果不在临时容器中进行更新,前一个更新会影响同一时间步内后一个更新的输出,这可能是不可取的。
完成!没有 lodash,没有打字稿,没有 jQuery,只是 ES6 所要求的和通用的。它看起来很粗糙,但是如果你编写一个通用的 recursiveCopy() 脚本,你可以轻松地编写一个函数来使用它来创建一个 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()
如何使用 ES6 克隆 Javascript class 实例。
我对基于 jquery 或 $extend 的解决方案不感兴趣。
我已经看到关于对象克隆的很早的讨论表明这个问题很复杂,但是对于 ES6 来说,一个非常简单的解决方案本身就出现了——我将把它放在下面,看看人们是否认为它令人满意。
编辑:有人认为我的问题是重复的;我看到了那个答案,但它已有 7 年历史,涉及使用 ES6 之前的 js 的非常复杂的答案。我建议我的问题(允许 ES6)有一个非常简单的解决方案。
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );
注意Object.assign的特点:浅拷贝,不拷贝class方法
如果你想要深拷贝或对拷贝有更多的控制,那么有lodash clone functions。
很复杂;我试了很多!最后,这个单行代码适用于我的自定义 ES6 class 个实例:
let clone = Object.assign(Object.create(Object.getPrototypeOf(orig)), orig)
它避免设置原型,因为 they say 它会大大降低代码速度。
它支持符号,但不适合 getters/setters,并且不能使用不可枚举的属性(参见 Object.assign() docs)。此外,克隆基本的内部 classes(如 Array、Date、RegExp、Map 等)遗憾的是似乎经常需要一些单独的处理。
结论:一团糟。让我们希望有一天会有一个本机和干净的克隆功能。
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 中不建议对原型进行扩展,这会导致在 code/components 上进行测试时出现问题。单元测试框架不会自动假设你的原型扩展。所以这不是一个好习惯。 这里有更多关于原型扩展的解释Why is extending native objects a bad practice?
要克隆 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();
result: Cassio Seffrin
cloneWithPrototype.logFullName();
result: Cassio Seffrin
original.address.street;
result: 'Street B, 99' // notice that original sub object was changed
注意:如果实例将闭包作为自己的属性,则此方法不会包装它。 (read more about closures)另外,子对象“地址”不会被克隆。
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 或 underscore。 他们将创建一个新对象并将每个值从原始对象复制到新对象,并在内存中保留其引用。
下划线: 让 cloneUnderscore = _(original).clone();
Loadash 克隆: var cloneLodash = _.cloneDeep(原版);
lodash 或 underscore 的缺点是需要在项目中包含一些额外的库。然而,它们是不错的选择,而且还能产生高性能结果。
您可以使用扩展运算符,例如,如果您想克隆一个名为 Obj 的对象:
let clone = { ...obj};
如果您想更改克隆对象或向其添加任何内容:
let clone = { ...obj, change: "something" };
另一种班轮:
大多数时候...(适用于 Date、RegExp、Map、String、Number、Array),顺便说一句,克隆字符串、数字有点搞笑。
let clone = new obj.constructor(...[obj].flat())
对于那些 class 没有复制构造函数的人:
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);
几乎所有答案我都喜欢。我遇到了这个问题,为了解决它,我会通过定义一个 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 相互扩展,克隆每个实例的最佳解决方案是定义一个函数来在其 class 定义中创建该对象的新实例,例如:
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)
我用的是 lodash。
import _ from 'lodash'
class Car {
clone() { return _.cloneDeep(this); }
}
这是对 OP 的更完整的回答,因为到目前为止收到的所有回答都存在问题(并不是说它们有时不适用于不同的案例和场景,只是它们不是根据要求仅使用 ES6 的最简单的通用答案)。为了子孙后代。
Object.assign() 只会进行浅拷贝,如 answer-er 所述。这实际上是一个大问题,因为 javascript 垃圾收集仅在所有引用都从原始对象中删除时才有效。这意味着任何引用旧对象的克隆,即使是很少更改的简单布尔值,也意味着潜在的严重内存泄漏。
Class 使用“clone()”方法扩展具有与 Object.assign() 相同的垃圾收集问题,如果您正在创建可能引用旧实例的新实例,即使 1 sub-tree 对象中存在数据。这很难单独管理。
使用扩展运算符(“...”)也是arrays/objects的浅拷贝,与上面的引用和唯一性问题相同。此外,正如对答案的回应中也提到的那样,这会丢失原型并且 class 无论如何
原型绝对是较慢的方法,但我相信 V8 已经解决了这种方法的性能问题,所以我不确定它在 2022 年是否还会成为问题。
2022 年的建议答案:正确编写深层复制脚本以获取所有 class 对象数据。当想要克隆一个 class 对象时,创建一个临时容器并将 class 对象深拷贝到临时容器中。写一个包含所有方法的父 class (superclass),以及你想要的对象数据和实例的子class。然后当从扩展 subclass 调用父方法时,将 subclass 的“this”作为参数传入,并在父方法中捕获该参数(我使用“that”这个词,例如)。最后,当您将对象数据克隆到临时对象中时,为您要克隆的所有对象创建新实例,并将对旧实例的任何引用替换为新实例,以确保它不会在内存中逗留。例如,在我的示例中,我正在制作康威生命游戏的 hacky 版本。我会有一个名为“allcells”的数组,然后在每个 requestAnimationFrame(renderFunction) 上更新它时,我会将 allcells 深度复制到 temp,运行 每个单元格的 update(this) 方法调用父级的 update(that) 方法,然后创建新的 Cell(temp[0].x, temp[0].y, etc) 并将所有这些打包到一个数组中,我可以在完成所有更新后用它替换旧的“allcells”容器。在生命游戏示例中,如果不在临时容器中进行更新,前一个更新会影响同一时间步内后一个更新的输出,这可能是不可取的。
完成!没有 lodash,没有打字稿,没有 jQuery,只是 ES6 所要求的和通用的。它看起来很粗糙,但是如果你编写一个通用的 recursiveCopy() 脚本,你可以轻松地编写一个函数来使用它来创建一个 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()