I often see this pattern to define javascript objects
function Person(name) {
this.name = name;
}
Person.prototype.describe = function () {
return \"
Just like arxanas wrote in his comment. Data properties in prototype are more or less similar to class-level variables in traditional oop. And these are not used on a daily-basis, unless you have a very specific need. That's all.
There's nothing wrong with that code. This is supposedly what is meant:
function Person(name) {
this.name = name;
}
Person.prototype.age = 15; //<= adding a hardcoded property to the prototype
Now you will see this:
var pete = new Person('Pete'), mary = new Person('Mary');
pete.age; //=> 15
mary.age //=> 15
And most of the time, that's not what you want. Properties assigned to the prototype of a constructor are shared between all instances, properties assigned within the constructor (this.name
) are specific for the instance.
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.
As arxanas says, the article mentions data properties.
The reason, I assume, is that data is typically specific to an instance, so it does not make sense to add it to the prototype.
Furthermore, if your data is of a mutable type, e.g. an array, and you assign it to the prototype, then this array instance is shared between all instances and you cannot use it as if every instance had its own array.
Example: The following leads to incorrect behaviour:
function Set() {
}
// shared between instances
// each instance adds values to **the same** array
Set.prototype.elements = [];
Set.prototype.add = function(x) {
this.elements.push(x);
};
It should be:
function Set() {
// each instance gets its own array
this.elements = [];
}
Set.prototype.add = function(x) {
this.elements.push(x);
};
To sum it up:
Perhaps related: In general modifying an object that you don't own is considered an anti pattern.
Meaning, if you didn't create the object then you don't "own" that object. Including:
window
)Source Maintainable Javascript by Nicholas C. Zakas
In usual object-oriented languages, you have a definition of the class describing members, methods and the constructor.
In JS, the definition of the "class" (it is not really class like in other languages... sometimes the term pseudoclass is used) is the constructor itself. If your object is parametrised by name
, it makes sense to write
function Person(name) {
this.name = name;
}
i.e. the property name
must be set in the constructor.
Of course, you can write
function Person(name) {
this.name = name;
this.describe = function() { ... };
}
and it will work as you expect.
However, in this case you are creating a separate instance of the method with every call of the constructor.
On the other hand, here:
Person.prototype.describe = function () {
return "Person called "+this.name;
};
you only define the method once. All instances of Person
will receive a pointer (called __proto__
and not accessible by programmer in most browsers) to Person.prototype
. So if you call
var myPerson = new Person();
myPerson.describe();
it will work, because JS looks object members directly in the object, then in its prototype etc. all the way up to Object.prototype
.
The point is that in the second case, only one instance of the function will exist. Which you will probably agree that is a better design. And even if you don't, it simply takes less memory.