Continuing with the example of the class Point of the previous section,
suppose we want to extend the capabilities of a point by giving it an annotation that shows up in the label.
We can achieve that by deriving a class AnnotatedPoint from Point . This may not
be a shining example of object-oriented analysis and design, but for the sake of illustration, please humor me.
class AnnotatedPoint extends Point { // Annotation, to be displayed in the label private String annotation; // Constructor from coordinates and annotation public AnnotatedPoint(double x, double y, String annotation) { super(x, y); this.annotation = annotation; } // Get label (coordinates + annotation) public String getLabel() { return new StringBuilder(super.getLabel()) .append(" ").append(annotation).toString(); } }To see how this is done in pre-ECMAScript-6 JavaScript, let us first look at the constructor function for annotated points: function AnnotatedPoint(x, y, annotation) { Point.call(this, x, y); this.annotation = annotation; }When this constructor function is called with the new operator, as in
var p = new AnnotatedPoint(0.0, 0.0, "Origin");the first thing that happens is that a new object is created, and this is bound
to that object. Then the function body is executed. The first thing that needs to happen in
the function body is to initialize the AnnotatedPoint instantiation as a Point
instantiation. That's what the call to the constructor function Point does: it performs
its initialization work with this bound to our new annotated point. This is why
it's important that constructor functions can be called not only with the new operator,
which causes them to create a new object, but also as ordinary functions, with this
bound to an already existing object. The rest of AnnotatedPoint 's body is obvious:
create and initialize the additional field annotation .
So far, we have made sure that an annotated point inherits all instance properties, that is,
all non-static fields, of a point. Next, we need to ensure that annotated points inherit
the methods and static fields of points, that is, the things that are stored in
If you know that your JavaScript program will run in an environment that supports the ECMAScript
Version 5 standard, then the method var newObject = Object.create(proto);it creates and returns a new object with proto as the first prototype in its property lookup chain.
Therefore, we can insert Point.prototype into the property lookup chain for annotated
points with just this one line of code:
AnnotatedPoint.prototype = Object.create(Point.prototype);This will result in the correct property lookup chain for AnnotatedPoint
as shown in Figure 3. Notice that the linking
mechanism of the property lookup chain is an implementation detail that is handled by
Object.create . We do not and should not know how it's done.
If you need to support pre-ES5 legacy environments as well, then you have to check if
AnnotatedPoint.prototype = makeDerivedPrototype(Point.prototype);where the function makeDerivedPrototype is defined as
function makeDerivedPrototype(basePrototype) { var derivedPrototype = null; if(Object.create !== undefined) { derivedPrototype = Object.create(basePrototype); } else { function EmptyObject(){} EmptyObject.prototype = basePrototype; derivedPrototype = new EmptyObject(); } return derivedPrototype; }It is a worthwhile exercise to convince yourself that the legacy code above results in the proper lookup chain for annotated points; however, you may not want to think about it at all as the preferred way is to delegate everything to the method Object.create .
To complete the AnnotatedPoint.prototype.getLabel = function() { var pointLabel = Point.prototype.getLabel.call(this); return pointLabel + " " + this.annotation; };For annotated points, the above version of getLabel will override
the getLabel method of Point simply because
AnnotatedPoint.prototype comes before Point.prototype
in the property lookup chain. The call to the base version
in the first line of the function body deserves some attention. Here, we must
make sure that the receiver of the call is the the annotated point whose label
we are retrieving. Since the method is overridden, the only way to achieve that
is to set the receiver explicitly via the call method. Had we decided
to make a whole new method, say, getAnnotatedLabel , rather than overriding
getLabel , the code would have been a bit simpler:
AnnotatedPoint.prototype.getAnnotatedLabel = function() { return this.getLabel() + " " + this.annotation; };Here's the complete pre-ECMAScript-6 JavaScript implementation of AnnotatedPoint :
// // The equivalent of the AnnotatedPoint class in pre-ECMAScript-6 JavaScript // function AnnotatedPoint(x, y, annotation) { Point.call(this, x, y); this.annotation = annotation; } // ES5 only AnnotatedPoint.prototype = Object.create(Point.prototype); AnnotatedPoint.prototype.getLabel = function() { var pointLabel = Point.prototype.getLabel.call(this); return pointLabel + " " + this.annotation; };In object-oriented programming, an inheritance relation constitutes an "is-a" relationship: every annotated point is a point. JavaScript's instanceof
operator recognizes that:
var ap = new AnnotatedPoint(0.0, 0.0, "Origin"); ap instanceof AnnotatedPoint; //true ap instanceof Point; //true |