Skip to content Skip to sidebar Skip to footer

Why Defining Properties In The Prototype Is Considered An Antipattern

I often see this pattern to define javascript objects function Person(name) { this.name = name; } Person.prototype.describe = function () { return 'Person called '+this.nam

Solution 1:

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

functionPerson(name) {
    this.name = name;
}

i.e. the property name must be set in the constructor.

Of course, you can write

functionPerson(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.

Solution 2:

There's nothing wrong with that code. This is supposedly what is meant:

functionPerson(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.

Solution 3:

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:

functionSet() {

}

// shared between instances// each instance adds values to **the same** arraySet.prototype.elements = [];

Set.prototype.add = function(x) {
   this.elements.push(x);
};

It should be:

functionSet() {
    // each instance gets its own arraythis.elements = [];
}

Set.prototype.add = function(x) {
   this.elements.push(x);
};

To sum it up:

  • Add properties that should be shared between all instances to the prototype.
  • Assign instance specific data inside the constructor function.

Solution 4:

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.

Solution 5:

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
 **/functionPoint(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
 **/functionPoint(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.

Post a Comment for "Why Defining Properties In The Prototype Is Considered An Antipattern"