Related
How do I clone a Javascript class instance using ES6.
I'm not interested in solutions based on jquery or $extend.
I've seen quite old discussions of object cloning that suggest that the problem is quite complicated, but with ES6 a very simple solution presents itself - I will put it below and see if people think it is satisfactory.
edit: it is being suggested that my question is a duplicate; I saw that answer but it is 7 years old and involves very complicated answers using pre-ES6 js. I'm suggesting that my question, which allows for ES6, has a dramatically simpler solution.
It is complicated; I tried a lot! In the end, this one-liner worked for my custom ES6 class instances:
let clone = Object.assign(Object.create(Object.getPrototypeOf(orig)), orig)
It avoids setting the prototype because they say it slows down the code a lot.
It supports symbols but isn't perfect for getters/setters and isn't working with non-enumerable properties (see Object.assign() docs). Also, cloning basic internal classes (like Array, Date, RegExp, Map, etc.) sadly often seems to need some individual handling.
Conclusion: It is a mess. Let's hope that there will one day be a native and clean clone functionality.
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );
Note the characteristics of Object.assign: it does a shallow copy and does not copy class methods.
If you want a deep copy or more control over the copy then there are the lodash clone functions.
I like almost all the answers. I had this problem and to resolve it I would do it manually by defining a clone() method and inside it, I would build the whole object from scratch. For me, this makes sense because the resulted object will be naturally of the same type as the cloned object.
Example with 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);
}
}
I like this solution because it looks more 'Object Oriented'y
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);
In Javascript it's not recommended to make extensions of the Prototype, It will result in issues when you will make tests on your code/components. The unit test frameworks will not assume automatically yours prototype extensions. So it isn't a good practice.
There are more explanations of prototype extensions here Why is extending native objects a bad practice?
To clone objects in JavaScript there is not a simple or straightforward way. Here is an the first instance using "Shallow Copy":
1 -> Shallow clone:
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
Results:
original.logFullName();
result: Cassio Seffrin
cloneWithPrototype.logFullName();
result: Cassio Seffrin
original.address.street;
result: 'Street B, 99' // notice that original sub object was changed
Notice: If the instance has closures as own properties this method will not wrap it. (read more about closures) And plus, the sub object "address" will not get cloned.
cloneWithoutPrototype.logFullName()
Will not work. The clone won't inherit any of the prototype methods of the original.
cloneWithPrototype.logFullName()
will work, because the clone will also copy its Prototypes.
To clone arrays with Object.assign:
let cloneArr = array.map((a) => Object.assign({}, a));
Clone array using ECMAScript spread sintax:
let cloneArrSpread = array.map((a) => ({ ...a }));
2 -> Deep Clone:
To archive a completely new object reference we can use JSON.stringify() to parse the original object as string and after parse it back to JSON.parse().
let deepClone = JSON.parse(JSON.stringify(original));
With deep clone the references to address will be keeped. However the deepClone Prototypes will be losed, therefore the deepClone.logFullName() will not work.
3 -> 3th party libraries:
Another options will be use 3th party libraries like loadash or underscore.
They will creates a new object and copies each value from the original to the new object keeping its references in memory.
Underscore:
let cloneUnderscore = _(original).clone();
Loadash clone:
var cloneLodash = _.cloneDeep(original);
The downside of lodash or underscore were the need to include some extra libraries in your project. However they are good options and also produces high performance results.
Create the copy of the object using the same prototype and the same properties as the original object.
function clone(obj) {
return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj))
}
Works with non-enumerable properties, getters, setters, etc. Is unable to clone internal slots, which many built-in javascript types have (e.g. Array, Map, Proxy)
if we have multiple class with extends from each other, best solution for clone of each instance is define one function to create new instance of that object in its class definition like :
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)
}
}
now i can use clone(0 function of object like :
let p = new ColorPoint(10,10,'red');
let pclone=p.clone();
Try this:
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
);
Since it uses Object.getOwnPropertyNames, it will also add non-enumerable properties.
Another one liner:
Most of the time...(works for Date, RegExp, Map, String, Number, Array), btw, cloning string, number is a bit funny.
let clone = new obj.constructor(...[obj].flat())
for those class without copy constructor:
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);
Is it not enough to do like this ?
Object.assign(new ClassName(), obj)
I used lodash.
import _ from 'lodash'
class Car {
clone() { return _.cloneDeep(this); }
}
This is the more complete answer to the OP since there are issues with all of the answers received thus far (not that they won’t work for different cases and scenarios some of the time, they’re just not the simplest universal answers using only ES6 as requested). For posterity.
Object.assign() will only do a shallow copy, as noted by the answer-er. This is actually a huge issue because javascript garbage collection only works when all references are remove from the original object. This means that any cloning done referencing an old object even for a simple bool that rarely changes means a potentially critical memory leak.
Class extends with a “clone()” method has the same garbage collection issues as Object.assign() if you’re creating new instances potentially referencing the old one if even 1 sub-tree of data exists in the object. This would be hard to manage on its own.
Using the spread operator (“...”) is also a shallow copy of arrays/objects, same problems with references and uniqueness as above. In addition, as also mentioned in responses to an answer, this loses the prototype and class anyway
Prototypes are definitely the slower method but V8 I believe has fixed performance issues with that approach so I’m not sure it’s an issue anymore in 2022.
SUGGESTED ANSWER FOR 2022: properly write a deep copy script to get all the class object data. When wanting to clone a class object create a temporary container and do a deep copy of class object into the temporary container. Write a parent class (superclass) with all of the methods in it, and the subclass you want for the object data and instances. Then when calling the parent’s method from the extended subclass, pass in the subclass’s ‘this’ as an argument and catch that argument in the parent’s method (I use the word ‘that’, for eg). Lastly, when you clone the object data into a temporary object, create new instances of all of the objects you want cloned, and replace any reference to the old instance with the new one to make sure it doesn’t linger in memory. In my example I’m making a hacky version of Conway’s Game of Life, for example. I would have an array called “allcells” then when updating it on each requestAnimationFrame(renderFunction) I would deep copy allcells into temp, run each cell’s update(this) method which calls the parent’s update(that) method, then create new Cell(temp[0].x, temp[0].y, etc) and package all those into an array which I can replace my old “allcells” container with after all the updates are done. In the game of life example, without doing the updates in a temp container the former updates would affect the outputs of the latter updates within the same time step, which may be undesirable.
Done! No lodash, no typescript, no jQuery, just ES6 as requested and universal. It looks gnarly, but if you write a generic recursiveCopy() script you could just as easily write a function to use it to make a clone() function if you want to following the steps I outlined above and using the example code below for reference.
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()
You can use spread operator, for instance if you want to clone an object named Obj:
let clone = { ...obj};
And if you want to change or add anything to the cloned object:
let clone = { ...obj, change: "something" };
I currently have this code, where speciesMap is a map in which the keys have maps as values.
var speciesMap = new Map();
...
if(!speciesMap.get(species)) {
let obj = new Map();
obj.set('count', 0);
obj.set('vol', 0);
speciesMap.set(species, obj);
}
I want to increase the count of a given specie by 1, but the only way I found to do it was like this.
speciesMap.get(species).set('count', speciesMap.get(species).get('count') + 1);
Is there a cleaner and simpler way to increase the value instead of going down the whole map again to retrieve its value? Same goes for volume, I need to go down the object and add + vol to it.
Wanted something like
speciesMap.get(species).set('count', this+1);
which obviously doesn't work.
You can use a helper function:
function update(map, key, callback) {
map.set(key, callback(map.get(key)));
}
Then your long expression becomes
update(speciesMap.get(species), 'count', v => v+1);
#Bergi's answer is fine, but it's Friday, so...
You can introduce a mutable number class and use instances of that class instead of bare numbers:
class Int {
constructor(n) {
this.n = n;
}
toString() {
return String(this.n);
}
valueOf() {
return this.n;
}
add(m) {
this.n += m;
return this;
}
}
let speciesMap = new Map();
speciesMap.set('elefant', new Map([
['count', new Int(0)],
['vol', new Int(0)]
]));
speciesMap.get('elefant').get('count').add(1);
alert('count is now ' + speciesMap.get('elefant').get('count'));
How do I clone a Javascript class instance using ES6.
I'm not interested in solutions based on jquery or $extend.
I've seen quite old discussions of object cloning that suggest that the problem is quite complicated, but with ES6 a very simple solution presents itself - I will put it below and see if people think it is satisfactory.
edit: it is being suggested that my question is a duplicate; I saw that answer but it is 7 years old and involves very complicated answers using pre-ES6 js. I'm suggesting that my question, which allows for ES6, has a dramatically simpler solution.
It is complicated; I tried a lot! In the end, this one-liner worked for my custom ES6 class instances:
let clone = Object.assign(Object.create(Object.getPrototypeOf(orig)), orig)
It avoids setting the prototype because they say it slows down the code a lot.
It supports symbols but isn't perfect for getters/setters and isn't working with non-enumerable properties (see Object.assign() docs). Also, cloning basic internal classes (like Array, Date, RegExp, Map, etc.) sadly often seems to need some individual handling.
Conclusion: It is a mess. Let's hope that there will one day be a native and clean clone functionality.
const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );
Note the characteristics of Object.assign: it does a shallow copy and does not copy class methods.
If you want a deep copy or more control over the copy then there are the lodash clone functions.
I like almost all the answers. I had this problem and to resolve it I would do it manually by defining a clone() method and inside it, I would build the whole object from scratch. For me, this makes sense because the resulted object will be naturally of the same type as the cloned object.
Example with 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);
}
}
I like this solution because it looks more 'Object Oriented'y
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);
In Javascript it's not recommended to make extensions of the Prototype, It will result in issues when you will make tests on your code/components. The unit test frameworks will not assume automatically yours prototype extensions. So it isn't a good practice.
There are more explanations of prototype extensions here Why is extending native objects a bad practice?
To clone objects in JavaScript there is not a simple or straightforward way. Here is an the first instance using "Shallow Copy":
1 -> Shallow clone:
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
Results:
original.logFullName();
result: Cassio Seffrin
cloneWithPrototype.logFullName();
result: Cassio Seffrin
original.address.street;
result: 'Street B, 99' // notice that original sub object was changed
Notice: If the instance has closures as own properties this method will not wrap it. (read more about closures) And plus, the sub object "address" will not get cloned.
cloneWithoutPrototype.logFullName()
Will not work. The clone won't inherit any of the prototype methods of the original.
cloneWithPrototype.logFullName()
will work, because the clone will also copy its Prototypes.
To clone arrays with Object.assign:
let cloneArr = array.map((a) => Object.assign({}, a));
Clone array using ECMAScript spread sintax:
let cloneArrSpread = array.map((a) => ({ ...a }));
2 -> Deep Clone:
To archive a completely new object reference we can use JSON.stringify() to parse the original object as string and after parse it back to JSON.parse().
let deepClone = JSON.parse(JSON.stringify(original));
With deep clone the references to address will be keeped. However the deepClone Prototypes will be losed, therefore the deepClone.logFullName() will not work.
3 -> 3th party libraries:
Another options will be use 3th party libraries like loadash or underscore.
They will creates a new object and copies each value from the original to the new object keeping its references in memory.
Underscore:
let cloneUnderscore = _(original).clone();
Loadash clone:
var cloneLodash = _.cloneDeep(original);
The downside of lodash or underscore were the need to include some extra libraries in your project. However they are good options and also produces high performance results.
Create the copy of the object using the same prototype and the same properties as the original object.
function clone(obj) {
return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj))
}
Works with non-enumerable properties, getters, setters, etc. Is unable to clone internal slots, which many built-in javascript types have (e.g. Array, Map, Proxy)
if we have multiple class with extends from each other, best solution for clone of each instance is define one function to create new instance of that object in its class definition like :
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)
}
}
now i can use clone(0 function of object like :
let p = new ColorPoint(10,10,'red');
let pclone=p.clone();
Try this:
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
);
Since it uses Object.getOwnPropertyNames, it will also add non-enumerable properties.
Another one liner:
Most of the time...(works for Date, RegExp, Map, String, Number, Array), btw, cloning string, number is a bit funny.
let clone = new obj.constructor(...[obj].flat())
for those class without copy constructor:
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);
Is it not enough to do like this ?
Object.assign(new ClassName(), obj)
I used lodash.
import _ from 'lodash'
class Car {
clone() { return _.cloneDeep(this); }
}
This is the more complete answer to the OP since there are issues with all of the answers received thus far (not that they won’t work for different cases and scenarios some of the time, they’re just not the simplest universal answers using only ES6 as requested). For posterity.
Object.assign() will only do a shallow copy, as noted by the answer-er. This is actually a huge issue because javascript garbage collection only works when all references are remove from the original object. This means that any cloning done referencing an old object even for a simple bool that rarely changes means a potentially critical memory leak.
Class extends with a “clone()” method has the same garbage collection issues as Object.assign() if you’re creating new instances potentially referencing the old one if even 1 sub-tree of data exists in the object. This would be hard to manage on its own.
Using the spread operator (“...”) is also a shallow copy of arrays/objects, same problems with references and uniqueness as above. In addition, as also mentioned in responses to an answer, this loses the prototype and class anyway
Prototypes are definitely the slower method but V8 I believe has fixed performance issues with that approach so I’m not sure it’s an issue anymore in 2022.
SUGGESTED ANSWER FOR 2022: properly write a deep copy script to get all the class object data. When wanting to clone a class object create a temporary container and do a deep copy of class object into the temporary container. Write a parent class (superclass) with all of the methods in it, and the subclass you want for the object data and instances. Then when calling the parent’s method from the extended subclass, pass in the subclass’s ‘this’ as an argument and catch that argument in the parent’s method (I use the word ‘that’, for eg). Lastly, when you clone the object data into a temporary object, create new instances of all of the objects you want cloned, and replace any reference to the old instance with the new one to make sure it doesn’t linger in memory. In my example I’m making a hacky version of Conway’s Game of Life, for example. I would have an array called “allcells” then when updating it on each requestAnimationFrame(renderFunction) I would deep copy allcells into temp, run each cell’s update(this) method which calls the parent’s update(that) method, then create new Cell(temp[0].x, temp[0].y, etc) and package all those into an array which I can replace my old “allcells” container with after all the updates are done. In the game of life example, without doing the updates in a temp container the former updates would affect the outputs of the latter updates within the same time step, which may be undesirable.
Done! No lodash, no typescript, no jQuery, just ES6 as requested and universal. It looks gnarly, but if you write a generic recursiveCopy() script you could just as easily write a function to use it to make a clone() function if you want to following the steps I outlined above and using the example code below for reference.
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()
You can use spread operator, for instance if you want to clone an object named Obj:
let clone = { ...obj};
And if you want to change or add anything to the cloned object:
let clone = { ...obj, change: "something" };
i try to extend Array object in javascript with some user friendly methods like Array.Add() instead Array.push() etc...
i implement 3 ways to do this.
unfortunetly the 3rd way is not working and i want to ask why? and how to do it work.
//------------- 1st way
Array.prototype.Add=function(element){
this.push(element);
};
var list1 = new Array();
list1.Add("Hello world");
alert(list1[0]);
//------------- 2nd way
function Array2 () {
//some other properties and methods
};
Array2.prototype = new Array;
Array2.prototype.Add = function(element){
this.push(element);
};
var list2 = new Array2;
list2.Add(123);
alert(list2[0]);
//------------- 3rd way
function Array3 () {
this.prototype = new Array;
this.Add = function(element){
this.push(element);
};
};
var list3 = new Array3;
list3.Add(456); //push is not a function
alert(list3[0]); // undefined
in 3rd way i want to extend the Array object internally Array3 class.
How to do this so not to get "push is not a function" and "undefined"?
Here i add a 4th way.
//------------- 4th way
function Array4 () {
//some other properties and methods
this.Add = function(element){
this.push(element);
};
};
Array4.prototype = new Array();
var list4 = new Array4();
list4.Add(789);
alert(list4[0]);
Here again i have to use prototype.
I hoped to avoid to use extra lines outside class constructor as Array4.prototype.
I wanted to have a compact defined class with all pieces in one place.
But i think i cant do it otherwise.
ES6
class SubArray extends Array {
last() {
return this[this.length - 1];
}
}
var sub = new SubArray(1, 2, 3);
sub // [1, 2, 3]
sub instanceof SubArray; // true
sub instanceof Array; // true
Using __proto__
(old answer, not recommended, may cause performance issues)
function SubArray() {
var arr = [ ];
arr.push.apply(arr, arguments);
arr.__proto__ = SubArray.prototype;
return arr;
}
SubArray.prototype = new Array;
Now you can add your methods to SubArray
SubArray.prototype.last = function() {
return this[this.length - 1];
};
Initialize like normal Arrays
var sub = new SubArray(1, 2, 3);
Behaves like normal Arrays
sub instanceof SubArray; // true
sub instanceof Array; // true
Method names should be lowercase. Prototype should not be modified in the constructor.
function Array3() { };
Array3.prototype = new Array;
Array3.prototype.add = Array3.prototype.push
in CoffeeScript
class Array3 extends Array
add: (item)->
#push(item)
If you don't like that syntax, and you HAVE to extend it from within the constructor,
Your only option is:
// define this once somewhere
// you can also change this to accept multiple arguments
function extend(x, y){
for(var key in y) {
if (y.hasOwnProperty(key)) {
x[key] = y[key];
}
}
return x;
}
function Array3() {
extend(this, Array.prototype);
extend(this, {
Add: function(item) {
return this.push(item)
}
});
};
You could also do this
ArrayExtenstions = {
Add: function() {
}
}
extend(ArrayExtenstions, Array.prototype);
function Array3() { }
Array3.prototype = ArrayExtenstions;
In olden days, 'prototype.js' used to have a Class.create method. You could wrap all this is a method like that
var Array3 = Class.create(Array, {
construct: function() {
},
Add: function() {
}
});
For more info on this and how to implement, look in the prototype.js source code
A while ago I read the book Javascript Ninja written by John Resig, the creator of jQuery.
He proposed a way to mimic array-like methods with a plain JS object. Basically, only length is required.
var obj = {
length: 0, //only length is required to mimic an Array
add: function(elem){
Array.prototype.push.call(this, elem);
},
filter: function(callback) {
return Array.prototype.filter.call(this, callback); //or provide your own implemetation
}
};
obj.add('a');
obj.add('b');
console.log(obj.length); //2
console.log(obj[0], obj[1]); //'a', 'b'
I don't mean it's good or bad. It's an original way of doing Array operations. The benefit is that you do not extend the Array prototype.
Keep in mind that obj is a plain object, it's not an Array. Therefore obj instanceof Array will return false. Think obj as a façade.
If that code is of interest to you, read the excerpt Listing 4.10 Simulating array-like methods.
In your third example you're just creating a new property named prototype for the object Array3. When you do new Array3 which should be new Array3(), you're instantiating that object into variable list3. Therefore, the Add method won't work because this, which is the object in question, doesn't have a valid method push. Hope you understand.
Edit: Check out Understanding JavaScript Context to learn more about this.
You can also use this way in ES6:
Object.assign(Array.prototype, {
unique() {
return this.filter((value, index, array) => {
return array.indexOf(value) === index;
});
}
});
Result:
let x = [0,1,2,3,2,3];
let y = x.unique();
console.log(y); // => [0,1,2,3]
Are you trying to do something more complicated then just add an alias for "push" called "Add"?
If not, it would probably be best to avoid doing this. The reason I suggest this is a bad idea is that because Array is a builtin javascript type, modifying it will cause all scripts Array type to have your new "Add" method. The potential for name clashes with another third party are high and could cause the third party script to lose its method in favour of your one.
My general rule is to make a helper function to work on the Array's if it doesnt exist somewhere already and only extend Array if its extremely necessary.
You CANNOT extend the Array Object in JavaScript.
Instead, what you can do is define an object that will contain a list of functions that perform on the Array, and inject these functions into that Array instance and return this new Array instance. What you shouldn't do is changing the Array.prototype to include your custom functions upon the list.
Example:
function MyArray() {
var tmp_array = Object.create(Array.prototype);
tmp_array = (Array.apply(tmp_array, arguments) || tmp_array);
//Now extend tmp_array
for( var meth in MyArray.prototype )
if(MyArray.prototype.hasOwnProperty(meth))
tmp_array[meth] = MyArray.prototype[meth];
return (tmp_array);
}
//Now define the prototype chain.
MyArray.prototype = {
customFunction: function() { return "blah blah"; },
customMetaData: "Blah Blah",
}
Just a sample code, you can modify it and use however you want. But the underlying concept I recommend you to follow remains the same.
var SubArray = function() {
var arrInst = new Array(...arguments); // spread arguments object
/* Object.getPrototypeOf(arrInst) === Array.prototype */
Object.setPrototypeOf(arrInst, SubArray.prototype); //redirectionA
return arrInst; // now instanceof SubArray
};
SubArray.prototype = {
// SubArray.prototype.constructor = SubArray;
constructor: SubArray,
// methods avilable for all instances of SubArray
add: function(element){return this.push(element);},
...
};
Object.setPrototypeOf(SubArray.prototype, Array.prototype); //redirectionB
var subArr = new SubArray(1, 2);
subArr.add(3); subArr[2]; // 3
The answer is a compact workaround which works as intended in all supporting browsers.
This question already has answers here:
JavaScript hashmap equivalent
(17 answers)
Closed 6 years ago.
How can you create the JavaScript/JQuery equivalent of this Java code:
Map map = new HashMap(); //Doesn't not have to be a hash map, any key/value map is fine
map.put(myKey1, myObj1);
map.put(myKey2, myObj2); //Repeat n times
function Object get(k) {
return map.get(k);
}
Edit: Out of date answer, ECMAScript 2015 (ES6) standard javascript has a Map implementation, read here for more info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
var map = new Object(); // or var map = {};
map[myKey1] = myObj1;
map[myKey2] = myObj2;
function get(k) {
return map[k];
}
//map[myKey1] == get(myKey1);
Just use plain objects:
var map = { key1: "value1", key2: "value2" }
function get(k){
return map[k];
}
function Map() {
this.keys = new Array();
this.data = new Object();
this.put = function (key, value) {
if (this.data[key] == null) {
this.keys.push(key);
}
this.data[key] = value;
};
this.get = function (key) {
return this.data[key];
};
this.remove = function (key) {
this.keys.remove(key);
this.data[key] = null;
};
this.each = function (fn) {
if (typeof fn != 'function') {
return;
}
var len = this.keys.length;
for (var i = 0; i < len; i++) {
var k = this.keys[i];
fn(k, this.data[k], i);
}
};
this.entrys = function () {
var len = this.keys.length;
var entrys = new Array(len);
for (var i = 0; i < len; i++) {
entrys[i] = {
key: this.keys[i],
value: this.data[i]
};
}
return entrys;
};
this.isEmpty = function () {
return this.keys.length == 0;
};
this.size = function () {
return this.keys.length;
};
}
This is an old question, but because the existing answers could be very dangerous, I wanted to leave this answer for future folks who might stumble in here...
The answers based on using an Object as a HashMap are broken and can cause extremely nasty consequences if you use anything other than a String as the key. The problem is that Object properties are coerced to Strings using the .toString method. This can lead to the following nastiness:
function MyObject(name) {
this.name = name;
};
var key1 = new MyObject("one");
var key2 = new MyObject("two");
var map = {};
map[key1] = 1;
map[key2] = 2;
If you were expecting that Object would behave in the same way as a Java Map here, you would be rather miffed to discover that map only contains one entry with the String key [object Object]:
> JSON.stringify(map);
{"[object Object]": 2}
This is clearly not a replacement for Java's HashMap. Bizarrely, given it's age, Javascript does not currently have a general purpose map object. There is hope on the horizon, though: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map although a glance at the Browser Compatability table there will show that this isn't ready to used in general purpose web apps yet.
In the meantime, the best you can do is:
Deliberately use Strings as keys. I.e. use explicit strings as keys rather than relying on the implicit .toString-ing of the keys you use.
Ensure that the objects you are using as keys have a well-defined .toString() method that suits your understanding of uniqueness for these objects.
If you cannot/don't want to change the .toString of the key Objects, when storing and retrieving the entries, convert the objects to a string which represents your understanding of uniqueness. E.g. map[toUniqueString(key1)] = 1
Sometimes, though, that is not possible. If you want to map data based on, for example File objects, there is no reliable way to do this because the attributes that the File object exposes are not enough to ensure its uniqueness. (You may have two File objects that represent different files on disk, but there is no way to distinguish between them in JS in the browser). In these cases, unfortunately, all that you can do is refactor your code to eliminate the need for storing these in a may; perhaps, by using an array instead and referencing them exclusively by index.
var map = {'myKey1':myObj1, 'mykey2':myObj2};
// You don't need any get function, just use
map['mykey1']
If you're not restricted to JQuery, you can use the prototype.js framework. It has a class called Hash: You can even use JQuery & prototype.js together. Just type jQuery.noConflict();
var h = new Hash();
h.set("key", "value");
h.get("key");
h.keys(); // returns an array of keys
h.values(); // returns an array of values