This blog post illustrates several JavaScript inheritance topics via an example: We start with naive implementations of a constructor Point and its sub-constructor ColorPoint and then improve them, step by step.
var point = { x: 5, y: 2, dist: function () { return Math.sqrt((this.x*this.x)+(this.y*this.y)); }, toString: function () { return "("+this.x+", "+this.y+")"; } };The syntactic construct with the curly braces that creates the point is called an object initializer or an object literal. The object point has four properties (slots for data): x, y, dist, and toString. You can read the values of properties by writing the name of an object, followed by a dot, followed by the name of the property:
> point.x 5Properties whose value are functions are called methods. Methods can be called by putting parentheses with arguments behind the name of a property:
> point.dist() 5.385164807134504 > point.toString() '(5, 2)'
function Point(x, y) { this.x = x; this.y = y; this.dist = function () { return Math.sqrt((this.x*this.x)+(this.y*this.y)); }; this.toString = function () { return "("+this.x+", "+this.y+")"; }; }When we execute new Point(), the constructor’s job is to set up the fresh object passed to it via the implicit parameter this. You can see that where we previously used an object initializer to define properties, we now add them via assignments to this. The fresh object is (implicitly) returned by the constructor and considered its instance:
> var p = new Point(3, 1); > p instanceof Point trueLike before, you can invoke methods and access non-method properties:
> p.toString() '(3, 1)' > p.x 3Methods shouldn’t be in each instance, they should be shared between instances, to save memory. You can use a prototype for that purpose. The object stored in Point.prototype becomes the prototype of the instances of Point. The relationship between an object (the “prototypee”) and its prototype works as follows: The prototypees inherit all of the prototype’s properties. In general, there is a single prototype and several prototypees, so all prototypees share the prototype’s properties. Consequently, Point.prototype is where you put the methods:
function Point(x, y) { this.x = x; this.y = y; } Point.prototype = { dist: function () { return Math.sqrt((this.x*this.x)+(this.y*this.y)); }, toString: function () { return "("+this.x+", "+this.y+")"; } }We assign an object to Point.prototype, via an object initializer with two properties dist and toString. Now there is a clear separation of responsibility: The constructor is responsible for setting up instance-specific data, the prototype contains shared data (i.e., the methods). Note that prototypes are highly optimized in JavaScript engines, so there is usually no performance penalty for putting methods there. Methods are called just like before, you don’t notice whether they are stored in the instance or in the prototype. One problem remains: For every function f the following equation should hold [1]:
f.prototype.constructor === fEvery function is set up like that by default. But we have replaced the default value of Point.prototype. To satisfy the equation, we can either add a property constructor to the object literal above or we can keep the default value, by not replacing it, by adding the methods to it:
function Point(x, y) { this.x = x; this.y = y; } Point.prototype.dist = function () { return Math.sqrt((this.x*this.x)+(this.y*this.y)); }; Point.prototype.toString = function () { return "("+this.x+", "+this.y+")"; };The constructor property is not that important; it mainly allows you to detect what constructor created a given instance:
> var p = new Point(2, 2) > p.constructor [Function: Point] > p.constructor.name 'Point'
function extend(target, source) { // Don’t do this: for (var propName in source) { target[propName] = source[propName]; } return target; }The problem with this code is that for-in iterates over all properties of an object, including those inherited from a prototype. This can be seen here:
> extend({}, new Point()) { x: undefined, y: undefined, dist: [Function], toString: [Function] }We do want the “own” (direct) properties x and y of the Point instance. But we don’t want its inherited properties dist and toString. Why are the inherited properties copied to the first argument? Because for-in sees all properties of an object, including inherited ones. Point inherits several properties from Object, for example valueOf:
> var p = new Point(7, 1); > p.valueOf [Function: valueOf]These properties are not copied over, because for-in can only see enumerable properties [2] and they are not enumerable:
> p.propertyIsEnumerable("valueOf") false > p.propertyIsEnumerable("dist") trueTo fix extend(), we must ensure that only own properties of source are considered.
function extend(target, source) { for (var propName in source) { // Is propName an own property of source? if (source.hasOwnProperty(propName)) { // (1) target[propName] = source[propName]; } } return target; }There is one more problem: The above code fails if source has an own property whose name is “hasOwnProperty” [3]:
> extend({}, { hasOwnProperty: 123 }) TypeError: Property 'hasOwnProperty' is not a functionThe failure is due to source.hasOwnProperty (line 1) accessing the own property (a number) instead of the inherited method. We can solve this problem by referring to that method directly and not via source:
function extend(target, source) { var hasOwnProperty = Object.prototype.hasOwnProperty; for (var propName in source) { // Invoke hasOwnProperty() with this = source if (hasOwnProperty.call(source, propName)) { target[propName] = source[propName]; } } return target; }On ECMAScript 5 engines (or older engines where a shim [4] has been loaded), the following version of extend() is better, because it preserves property attributes such as enumerability:
function extend(target, source) { Object.getOwnPropertyNames(source) .forEach(function(propName) { Object.defineProperty(target, propName, Object.getOwnPropertyDescriptor(source, propName)); }); return target; }
var proto = { bla: true }; var obj = Object.create(proto); obj.foo = 123; obj.bar = "abc";obj has both inherited and own properties:
> obj.bla true > obj.foo 123The ECMAScript 5 shim uses code similar to the following to make Object.create available on older browsers.
if (Object.create === undefined) { Object.create = function (proto) { function Tmp() {} Tmp.prototype = proto; // New empty object whose prototype is proto return new Tmp(); }; }The above code uses a temporary constructor to create a single instance that has the given prototype. So far, we have ignored the optional second parameter of Object.create() that allows you to define properties on the newly created object:
var obj = Object.create(proto, { foo: { value: 123 }, bar: { value: "abc" } });The properties are defined via property descriptors. With a descriptor, you can specify property attributes such as enumerability, not just values. As an exercise, let us implement protoChain(), a simplified version of Object.create(). It avoids the complexities of property descriptors and simply extends the new object with the second parameter. For example:
var obj = protoChain(proto, { foo: 123, bar: "abc" });We can generalize the above idea to an arbitrary amount of parameters:
protoChain(obj_0, obj_1, ..., obj_n-1, obj_n)Remember that we have to create fresh objects in order to assign prototypes. Hence, protoChain() returns a shallow copy of obj_n whose prototype is a shallow copy of obj_n-1, etc. obj_0 is the only object in the returned chain that has not been duplicated. protoChain() can be implemented like this:
function protoChain() { if (arguments.length === 0) return null; var prev = arguments[0]; for(var i=1; i < arguments.length; i++) { // Create duplicate of arguments[i] with prototype prev prev = Object.create(prev); extend(prev, arguments[i]); } return prev; }
function ColorPoint(x, y, color) { Point.call(this, x, y); this.color = color; }The above code sets up the instance properties x, y and color. It does so by passing this (an instance of ColorPoint) to Point: Point is called as a function, but the call() method allows us to keep the this of ColorPoint. Therefore, Point() adds x and y for us and we add color ourselves. We still need to take care of methods: On one hand, we want to inherit Point’s methods, on the other hand, we want to define our own methods. This is a simple way of doing so via extend():
// function ColorPoint: see above extend(ColorPoint.prototype, Point.prototype); ColorPoint.prototype.toString = function () { return this.color+" "+Point.prototype.toString.call(this); };We first copy the methods in Point.prototype to ColorPoint.prototype and then add our own method: We replace Point’s toString() with a version whose result combines the color with the output of Point.prototype.toString(). We directly refer to the latter method and call it with ColorPoint’s this. For more information on invoking methods of a super-prototype consult [5]. ColorPoint works as expected:
> var cp = new ColorPoint(5, 3, "red"); > cp.toString() 'red (5, 3)'As an improvement, we can avoid adding redundant properties to ColorPoint.prototype, by making Point.prototype its prototype.
// function ColorPoint: see above ColorPoint.prototype = Object.create(Point.prototype); ColorPoint.prototype.constructor = ColorPoint; ColorPoint.prototype.toString = function () { return this.color+" "+Point.prototype.toString.call(this); };In line 1, we have replaced the default value of ColorPoint.prototype and thus need to set the constructor property in line 2. While writing a single constructor is fairly straightforward, the above code is too complicated to be performed by hand. A helper function inherits() would make it simpler:
// function ColorPoint: see above ColorPoint.prototype.toString = function () { return this.color+" "+Point.prototype.toString.call(this); }; inherits(ColorPoint, Point);The function inherits() is modeled after Node.js’s util.inherits(). It gives you subtyping while keeping the simplicity of normal constructors. Requirements:
function inherits(SubC, SuperC) { var subProto = Object.create(SuperC.prototype); // At the very least, we keep the "constructor" property // At most, we keep additions that have already been made extend(subProto, SubC.prototype); SubC.prototype = subProto; };
Point.prototype.toString.call(this)That is not ideal, because we have hard-coded the super-constructor. Instead, we’d rather use:
ColorPoint._super.toString.call(this)To make the above code possible, inherits() only has to make the following assignment:
SubC._super = SuperC.prototype;Here we diverge from Node.js, where SubC.super_ refers to SuperC. The ColorPoint constructor still contains a hard-coded reference to Point. It can be eliminated by replacing Point.call(...) with
ColorPoint._super.constructor.call(this, x, y);Not exactly pretty, but it gets the job done. Our final version of ColorPoint looks like this:
function ColorPoint(x, y, color) { ColorPoint._super.constructor.call(this, x, y); this.color = color; } ColorPoint.prototype.toString = function () { return this.color+" "+ColorPoint._super.toString.call(this); }; inherits(ColorPoint, Point);
Via our running example, we have seen how to go from objects to constructors, how to extend objects, how to set an object’s prototype and how to create sub-constructors. You can read the following blog posts to deepen your understanding of JavaScript inheritance: