Declaring properties on a prototype is not an anti pattern at all. When I look at a prototype
object, I think "this is what the prototypical object of this type has for data and methods."
Others have warned against giving properties a reference value in the prototype, for instance: Foo.prototype.bar = [];
--- because Arrays and Objects are reference types. A reference type is immutable, therefore every instance of a "class" refers to the same Array or Object. Just set them to null
in the prototype, then give them a value in the constructor.
I always include all properties in the prototype for one very clear reason: Communicating to other programmers what properties are publically available and what their default values are without requiring them to sift through a constructor to figure it out.
This becomes especially useful if you are creating a shared library that requires documentation.
Consider this example:
/**
* class Point
*
* A simple X-Y coordinate class
*
* new Point(x, y)
* - x (Number): X coordinate
* - y (Number): Y coordinate
*
* Creates a new Point object
**/
function Point(x, y) {
/**
* Point#x -> Number
*
* The X or horizontal coordinate
**/
this.x = x;
/**
* Point#y -> Number
*
* The Y or vertical coordinate
**/
this.y = y;
}
Point.prototype = {
constructor: Point,
/**
* Point#isAbove(other) -> bool
* - other (Point): The point to compare this to
*
* Checks to see if this point is above another
**/
isAbove: function(other) {
return this.y > other.y;
}
};
(Documentation format: PDoc)
Just reading the documentation is a little awkward here because the information about the x
and y
properties are embedded inside the constructor function. Contrast that with the "anti-pattern" of including these properties in the prototype:
/**
* class Point
*
* A simple X-Y coordinate class
*
* new Point(x, y)
* - x (Number): X coordinate
* - y (Number): Y coordinate
*
* Creates a new Point object
**/
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype = {
/**
* Point#x -> Number
*
* The X or horizontal coordinate
**/
x: 0,
/**
* Point#y -> Number
*
* The Y or vertical coordinate
**/
y: 0,
constructor: Point,
/**
* Point#isAbove(other) -> bool
* - other (Point): The point to compare this to
*
* Checks to see if this point is above another
**/
isAbove: function(other) {
return this.y > other.y;
}
};
Now looking at the prototype gives you a snapshot of the actual object, which is much easier to visualize in your head, and easier for the author to write the documentation. The constructor function is also not cluttered up with documentation and sticks to the business of bringing a Point
object to life.
The prototype has everything, and is the canonical source of information about what the "prototypical" Point
object has for both methods and data.
I would argue that not including data properties in the prototype is the anti pattern.