This blog post explains when you should and should not put data in prototype properties.
A constructor usually sets instance properties to initial values. If one such value is a default then you don’t need to create an instance property. You only need a prototype property with the same name whose value is the default. For example:
/**
* Anti-pattern: don’t do this
*
* @param data an array with names
*/
function Names(data) {
if (data) {
// There is a parameter
// => create instance property
this.data = data;
}
}
Names.prototype.data = [];
The parameter data is optional. If it is missing, the instance does not get a property data, but inherits Names.prototype.data, instead.
This approach mostly works: You can create an instance n of Names. Getting n.data reads Names.prototype.data. Setting n.data creates a new own property in n and preserves the shared default value in the prototype. We only have a problem if we change the default value (instead of replacing it with a new value):
> var n1 = new Names();
> var n2 = new Names();
> n1.data.push('jane'); // change default value
> n1.data
[ 'jane' ]
> n2.data
[ 'jane' ]
Explanation: push() changed the array in Names.prototype.data. Since that array is shared by all instances without an own property data, n2.data’s initial value has changed, too.
function Names(data) {
this.data = data || [];
}
Obviously, the problem of modifying a shared default value does not arise if that value is immutable (as all primitives [1] are). But for consistency’s sake, it’s best to stick to a single way of setting up properties. I also prefer to maintain the usual separation of concerns [2]: the constructor sets up the instance properties, the prototype contains the methods.
ECMAScript 6 will make this even more of a best practice, because constructor parameters can have default values and you can define prototype methods in class bodies, but not prototype properties with data.
function Names(data) {
if (data) this.data = data;
}
Names.prototype = {
constructor: Names,
get data() {
// Define, don’t assign [3]
// => ensures an own property is created
Object.defineProperty(this, 'data', {
value: [],
enumerable: true
// Default – configurable: false, writable: false
// Set to true if property’s value must be changeable
});
return this.data;
}
};
(As an aside, we have replaced the original object in Names.prototype, which is why we need to set up the property constructor [4].)
Obviously, that is quite a bit of work, so you have to be sure it is worth it.
Example: You can store a constant in a prototype property and access it via this.
function Foo() {}
Foo.prototype.FACTOR = 42; // primitive value, immutable
Foo.prototype.compute = function (x) {
return x * this.FACTOR;
};
This constant is not polymorphic. Therefore, you can just as well access it via a variable:
// This code should be inside an IIFE [5] or a module function Foo() {} var FACTOR = 42; // primitive value, immutable Foo.prototype.compute = function (x) { return x * FACTOR; };The same holds for storing mutable data in non-polymorphic prototype properties.
Mutable prototype properties are difficult to manage. If they are non-polymorphic then you can at least replace them with variables.
function ConstrA() { }
ConstrA.prototype.TYPE_NAME = 'ConstrA';
function ConstrB() { }
ConstrB.prototype.TYPE_NAME = 'ConstrB';
Thanks to the polymorphic “tag” TYPE_NAME, you can distinguish the instances of ConstrA and ConstrB even when they cross frames (then instanceof does not work [6]).