New sections 1.2, 2 and 3.
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]).