dgaviola
dgaviola

Reputation: 2469

Functions inside constructor vs prototype

I know there are similar questions like this, but I want to see if those answers are still valid given optimizations in new Javascript engines.

In my opinion, the biggest benefit about defining functions inside the constructor is that you can easily avoid having to know the value of 'this' keyword:

var Person = function() {
  var self = this;
  self.firstName = null;
  self.lastName = null;
  self.fullName = function() {
    return self.firstName + self.lastName;
  };
};

This approach is recommended by Knockout Managing 'this'. This is a great advantage, especially when the code is being modified by many developers as it is really simple to understand and use.

The other approach would be to use the object prototype:

var Person = function() {
  this.firstName = null;
  this.lastName = null;
};
Person.prototype.fullName = function() {
  return this.firstName + this.lastName;
};

In this case there are performance advantages because functions objects will be created once. However the main issue I have with it is that it might be complicated to handle the 'this' keyword. The above example is very simple, but if you have event handlers, forEach calls, jQuery each() calls, methods being call from different contexts, etc., it is easy to make a bad use of this.

Of course, if you understand how 'this' works and are aware of how methods are being called, you shouldn't have much issues. However, in my experience, this takes time and it is error prone, especially when code is crafted by many developers.

I know that new JS engines, like V8, are applying optimizations to cases where you declare functions inside the constructor by creating hidden classes: How the V8 engine works?.

So my question is, given these optimizations done by new JS engines and the complexity of having to handle the 'this' keyword, does it still make sense to use the prototype based approach? What I would loose by using the approach of putting everything inside the constructor?

UPDATE 1:

I just did a micro-benchmark on Chrome (version 42). I create 1M objects with functions inside constructor and functions in prototype. It is a very simple object with two variables and three functions and the results are like this:

Functions inside constructor: 1.91 seconds
Functions in prototype: 1.10 seconds

Sounds like even with those optimizations in V8 it is still 73% faster. However this was a micro-benchmark. Not sure if that will be a big difference in real world applications.

UPDATE 2:

I also took a look at memory consumption and there are big differences as well. For functions inside constructors:

Shallow size: 64,000,120
Retained size: 336,001,128

For prototype functions:

Shallow size: 40,000,000
Retained size: 40,000,000

Either optimizations with hidden class are not that good or I'm missing something about that. I'm using monomorphic code (constructors with no args) as suggested by V8, but not sure if I'm doing something wrong.

UPDATE 3:

Here is the link of the test I did in case someone can point out something wrong in there: http://jsperf.com/dg-constructor-vs-prototype

Upvotes: 14

Views: 7180

Answers (3)

Fouad Boukredine
Fouad Boukredine

Reputation: 1623

Like @Ersin Basaran mentioned, a function created inside the constructor is unique for every object instance, unlike when it is created using the prototype makes it the same function for every object instance.

However, after introducing classes in ES6 (ECMAScript2015), if you use a class to create a method instead of using a constructor function, and you create this method outside the constructor (but inside the class), it will be the same for every object instance, just like when using the prototype.

Here is an example of creating a fullName() method:

class Person {
    constructor () {
        var self = this;
        self.firstName = null;
        self.lastName = null;
    }
    fullName () {
        return self.firstName + self.lastName;
    }
}

Person.prototype.fullName2 = function () {
    return this.firstName + this.lastName;
};

var a = new Person();
var b = new Person();

console.log(a.fullName == b.fullName); // returns true
console.log(a.fullName2 == b.fullName2); // returns true

I hope this helps.

Upvotes: 2

JohnRC
JohnRC

Reputation: 1371

I have been using a slightly different approach which IMO has the advantage of clarity and also avoids creating the member functions afresh every time the constructor is called:

  • define member functions of classes as module-level functions outside of the constructor
  • explicitly assign the functions to the class instance within the constructor

For example:

// Constructor for MyClass
function MyClass(a, b){

    // set properties of the instance from constructor arguments
    this.a = a;
    this.b = b;

    // assign the report function as a member of this instance
    this.report = Report;

}

// Report function is defined at the module level,
// but used by assigning it to an instance variable
// within the constructor.
function Report(){
    console.log( "a=" + this.a, "b=" + this.b);
}

There will only be a single instance of the member function which is shared by all the instances of the same class - as is the case when assigning a function to class.prototype.function - so this approach is efficient, and has these additional advantages, IMO:

  1. The constructor explicitly includes declarations of all of the methods of the class, instead of having methods added to the prototype outside of the constructor.
  2. The code of the constructor less cluttered and easier to follow than it is if the whole function definition is coded within the constructor.
  3. As the method is defined at module level, it is instantiated before the module is executed, so the class can be referred to in code that precedes the constructor declaration. This is not the case when assigning to class.prototype.function, which must be executed before the constructor can be called.

Usage:

// Create instances of my class
var inst1 = new MyClass( "as", "df");
var inst2 = new MyClass( "gh", "jk");

// Report the instances
inst1.report();
inst2.report();

// Class and method declarations follow below here...

Are there any downsides to this approach?

Upvotes: 0

Ersin Basaran
Ersin Basaran

Reputation: 517

I perform a quick test. If you declare function in the constructor, two object instances have different function instances even after optimizations. However with prototype, you have only one instance of the function which explains the performance difference.

    var Person = function () {
        var self = this;
        self.firstName = null;
        self.lastName = null;
        self.fullName = function () {
            return self.firstName + self.lastName;
        };
    };

    Person.prototype.fullName2 = function () {
        return this.firstName + this.lastName;
    };

    var a = new Person();
    var b = new Person();

    console.log(a.fullName == b.fullName); // returns false
    console.log(a.fullName2 == b.fullName2); // returns true

Upvotes: 8

Related Questions