Advantages of using prototype, vs defining methods straight in the constructor?

91,800

Solution 1

Methods that inherit via the prototype chain can be changed universally for all instances, for example:

function Class () {}
Class.prototype.calc = function (a, b) {
    return a + b;
}

// Create 2 instances:
var ins1 = new Class(),
    ins2 = new Class();

// Test the calc method:
console.log(ins1.calc(1,1), ins2.calc(1,1));
// -> 2, 2

// Change the prototype method
Class.prototype.calc = function () {
    var args = Array.prototype.slice.apply(arguments),
        res = 0, c;

    while (c = args.shift())
        res += c;

    return res; 
}

// Test the calc method:
console.log(ins1.calc(1,1,1), ins2.calc(1,1,1));
// -> 3, 3

Notice how changing the method applied to both instances? This is because ins1 and ins2 share the same calc() function. In order to do this with public methods created during construction, you'd have to assign the new method to each instance that has been created, which is an awkward task. This is because ins1 and ins2 would have their own, individually created calc() functions.

Another side effect of creating methods inside the constructor is poorer performance. Each method has to be created every time the constructor function runs. Methods on the prototype chain are created once and then "inherited" by each instance. On the flip side of the coin, public methods have access to "private" variables, which isn't possible with inherited methods.

As for your function Class() {} vs var Class = function () {} question, the former is "hoisted" to the top of the current scope before execution. For the latter, the variable declaration is hoisted, but not the assignment. For example:

// Error, fn is called before the function is assigned!
fn();
var fn = function () { alert("test!"); } 

// Works as expected: the fn2 declaration is hoisted above the call
fn2();
function fn2() { alert("test!"); }

Solution 2

The advantage of the prototype approach is efficiency. There is one calc() function object shared between all Class objects (by which I mean objects created by calling the Class constructor). The other way (assigning methods within the constructor) creates a new function object for every Class object, using more memory and taking more processing time when calling the Class constructor. However, this approach does have an advantage: the calc() method has access to local variables within the constructor, which you can use to your advantage:

function Class() {
    var calcCallCount = 0;

    this.calc = function (a, b) {
        ++calcCallCount;
        alert("Calc called " + calcCallCount + " times");
        return a + b;
    };
};

Regarding var Class = function() {...} versus function Class() {...}, I generally prefer the latter is because it means the function has a name, which can be useful when debugging. The other difference is that the latter version (a function declaration) is hoisted, meaning that it is available everywhere within the scope in which it is defined, not just after the definition. However, some people prefer to use the former (a function expression) everywhere.

Solution 3

var YourClass = function(){
  var privateField = "somevalue";
  this.publicField = "somevalue";
  this.instanceMethod1 = function(){
     //you may access both private and public field from here:
     //in order to access public field, you must use "this":
     alert(privateField + "; " + this.publicField);
  };
}

YourClass.prototype.instanceMethod2 = function(){
  //you may access only public field 2 from this method, but not private fields:
  alert(this.publicField);
  //error: drawaback of prototype methods:
  alert(privateField);  
};

Advantages of prototype methods:

  1. When you define methods via prototype, they are shared among all YourClass instances. As a result the total size of such instances is < than if you define methods in constructor; There are tests that show how method definition via prototype decrease the total size of html page and as a result a speed of its loading.

  2. another advantage of methods, defined via prototype - is when you use inherited classes, you may override such methods and in the overriden method of the derived class you may invoke the method of base class with the same name, but with methods defined in constructor, you cannot do this.

Share:
91,800
Leo
Author by

Leo

Full-stack developer with passion for UXD/IA. 15y of web design/development experience, gone from technical to mgmt roles and back

Updated on February 11, 2020

Comments

  • Leo
    Leo over 4 years

    I am wondering if there are any advantages of using any of these over the other, and which way should I go?

    Constructor approach:

    var Class = function () {
    
        this.calc = function (a, b) {
            return a + b;
        };
    
    };
    

    Prototype approach:

    var Class = function () {};
    
    Class.prototype.calc = function (a, b) {
        return a + b;
    };
    

    I don't like that, using the prototype, method definitions are separated from the class, and I'm not aware if there is any specific reason I should use this over just the first approach.

    Also, is there any benefit of using a function literal to define a "class", over just function definition:

    var Class = function () {};
    

    vs

    function Class () {};
    

    Thanks!

  • Leo
    Leo over 13 years
    Aah, that makes things so much clearer :) I didn't realize the efficiency difference - that's very useful to know. Same for the hoisting effect - tricky, indeed. Thanks for such a great answer, I learned a lot from it!
  • Leo
    Leo over 13 years
    Thanks for your answer, I appreciate it and now realize even more how great a resource StackOverflow is.
  • Leo
    Leo over 13 years
    Thanks for your answer too, Tim, I appreciate it!
  • HMR
    HMR over 10 years
    You forgot to mention that it's harder to use Super.call.someFunction() when overriding in inheritance: stackoverflow.com/a/16063711/1641941
  • vol7ron
    vol7ron about 10 years
    Very old question, but somehow followed a link and stumbled here -- I think the example would be more telling if you kept the number of arguments consistent (just to demonstrate that it's using a+b. This really is a small point, but it helps the reader identify the diff that you're concentrating on as well as rule out other factors he may be reading (for instance: what happens in the first call if you did have a third argument). The example is simple enough and hopefully the programmer is good enough not to get caught up on the small differences.
  • Engineer
    Engineer almost 10 years
    Re Class = function() {...}, i.e. defining in global/window scope, I've not had any debugging problems with this approach in terms of name, although understandably hoisting does not seem to occur. Not sure if there were any other differences between this approach and your two.
  • Tim Down
    Tim Down almost 10 years
    @NickWiggill: Built-in browser developer tools have come a long way since I wrote this answer and they now do a much better job of inferring an appropriate function name from the context, so I agree that ease of debugging is much less of a concern these days.
  • Bernhard
    Bernhard over 9 years
    Made a js Perf test to visualize performance differences. jsperf.com/class-comparison
  • Eric Barr
    Eric Barr about 9 years
    @Bernhard just to clarify, all of the performance hit is in the instantiation of the object. I made this jsperf to highlight that: jsperf.com/class-create-this-vs-proto . Interestingly, if you're just instantiating one instance of the class and using it it looks like it's faster to use this.. However, if you're going to be instantiating a ton of that class you definitely want to use .prototype.. Thanks for the jsperf!
  • Yiling
    Yiling about 9 years
    To avoid anonymous function in function expression, you can instead do "var Class = function namedFunc( ) {...}"; this way your function still have a name and is assigned to a variable.
  • Tim Down
    Tim Down about 9 years
    @YilingLu: Yes, that's a named function expression and is generally fine. However, there are issues with it in old IE, which was much more of a problem in 2010 than it is today.
  • timebandit
    timebandit about 9 years
    Surely the same could be achieved using the Functional pattern and thus avoiding having to ape the inheritance patterns of classical languages.
  • Dave Voyles
    Dave Voyles over 8 years
    It should be noted, that you can still have private methods while making use of the prototype, as seen in this post: stackoverflow.com/questions/1441212/…
  • Tim Down
    Tim Down over 8 years
    @DaveVoyles: Or indeed this post: stackoverflow.com/questions/9772307/…
  • benjaminz
    benjaminz about 8 years
    Isn't it more dangerous in the prototypical approach? I can accidentally change function assignment for all classes without knowing it. Whereas the Constructor approach enforces that if you want to change a function, you'd have to find the code and change it.
  • Ren
    Ren about 8 years
    Hello, what do you mean by inherited classes? I don't think it's the right terminology cuz javascript has no concept of classes.. When you said overridden method of the derived class, did you mean, another object, whose prototype is your object? I'm lost.. Can you please edit or explain?
  • Govind Rai
    Govind Rai over 7 years
    Alexandr could you explain #2 with an example?
  • Jwan622
    Jwan622 over 6 years
    what is this part of the code doing: Class.prototype.calc = function () { var args = Array.prototype.slice.apply(arguments), res = 0, c;
  • Jonathan Rys
    Jonathan Rys about 6 years
    Since some time has passed and we now have ES6+, I'd like to note that arrow function definitions are shorthand for var Class = function() {...} and are therefore not hoisted either.
  • WynandB
    WynandB over 5 years
    @Jwan622, it converts arguments to an (real) array so we can use array functions such as shift(). Note that Object.prototype.toString.call(arguments) returns [object Arguments] while Object.prototype.toString.call([]) returns [object Array].
  • c0dezer019
    c0dezer019 about 2 years
    So, trying to understand here. Ideally you'd add methods/properties via prototype when you update a class vs rebuilding? But rebuilding would be required if new methods need access to private variables if I'm understanding correctly.