.clone()
vs. copy constructors.clone()
methodsIn this chapter, we look at two techniques for implementing copying for class instances:
.clone()
methods.clone()
methodsThis technique introduces one method .clone()
per class whose instances are to be copied. It returns a deep copy of this
. The following example shows three classes that can be cloned.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
clone() {
return new Point(this.x, this.y);
}
}
class Color {
constructor(name) {
this.name = name;
}
clone() {
return new Color(this.name);
}
}
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)
}
}
Line A demonstrates an important aspect of this technique: compound instance property values must also be cloned, recursively.
A copy constructor is a constructor that uses another instance of the current class to set up the current instance. Copy constructors are popular in static languages such as C++ and Java, where you can provide multiple versions of a constructor via static overloading. Here, static means that the choice which version to use, is made at compile time.
In JavaScript, we must make that decision at runtime and that leads to inelegant code:
class Point {
constructor(...args) {
if (args[0] instanceof Point) {
// Copy constructor
const [other] = args;
this.x = other.x;
this.y = other.y;
} else {
const [x, y] = args;
this.x = x;
this.y = y;
}
}
}
This is how you’d use this class:
const original = new Point(-1, 4);
const copy = new Point(original);
assert.deepEqual(copy, original);
Static factory methods are an alternative to constructors and work better in this case because we can directly invoke the desired functionality. (Here, static means that these factory methods are class methods.)
In the following example, the three classes Point
, Color
and ColorPoint
each have a static factory method .from()
:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static from(other) {
return new Point(other.x, other.y);
}
}
class Color {
constructor(name) {
this.name = name;
}
static from(other) {
return new Color(other.name);
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
static from(other) {
return new ColorPoint(
other.x, other.y, Color.from(other.color)); // (A)
}
}
In line A, we once again copy recursively.
This is how ColorPoint.from()
works:
const original = new ColorPoint(-1, 4, new Color('red'));
const copy = ColorPoint.from(original);
assert.deepEqual(copy, original);