5

Stylistically, I prefer this structure:

var Filter = function( category, value ){
  this.category = category;
  this.value = value;

  // product is a JSON object
  Filter.prototype.checkProduct = function( product ){
    // run some checks
    return is_match;
  }

};

To this structure:

var Filter = function( category, value ){
  this.category = category;
  this.value = value;
};// var Filter = function(){...}

Filter.prototype.checkProduct = function( product ){
  // run some checks
  return is_match;
}

Functionally, are there any drawbacks to structuring my code this way? Will adding a prototypical method to a prototype object inside the constructor function's body (i.e. before the constructor function's expression statement closes) cause unexpected scoping issues?

I've used the first structure before with success, but I want to make sure I'm not setting myself for a debugging headache, or causing a fellow developer grief and aggravation due to bad coding practices.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
James Heston
  • 923
  • 1
  • 8
  • 17
  • 2
    What comes to my mind is that Filter's prototype remains empty until Filter is invoked. What if somebody want's to inherit Filter prototype without invoking Filter first? But I am not sure if this deserves full answer. :) – Kuba Wyrostek Jan 31 '15 at 20:09
  • 2
    You're re-running all the code that sets up the prototype on every call to the constructor. – Pointy Jan 31 '15 at 20:10
  • 4
    It's running the same assignment over and over again each time the constructor is called so it's a bit wasteful in that regard. Perhaps someone else can think of a situation where it would actually cause a functionality problem, but I can't. – jfriend00 Jan 31 '15 at 20:10
  • @jfriend00: Everytime when one inadvertently accesses a local constructor variable from his prototype method through closure. It works perfectly fine in experiments with a single instance. But well… – Bergi Jan 31 '15 at 20:14
  • @Bergi - I don't follow your comment. Are you claiming a specific problem with the OP's first code block? – jfriend00 Jan 31 '15 at 20:18
  • @jfriend00: No, I mean the problem that you have laid out in your answer with the `Counter` example. It "works" with a single instance, but as soon as you instantiate a second object hell breaks loose… – Bergi Jan 31 '15 at 20:33
  • See also [Defining prototype methods inside the constructor](https://stackoverflow.com/q/7115594/1048572) – Bergi Apr 11 '21 at 17:14

6 Answers6

11

Functionally, are there any drawbacks to structuring my code this way? Will adding a prototypical method to a prototype object inside the constructor function's body (i.e. before the constructor function's expression statement closes) cause unexpected scoping issues?

Yes, there are drawbacks and unexpected scoping issues.

  1. Assigning the prototype over and over to a locally defined function, both repeats that assignment and creates a new function object each time. The earlier assignments will be garbage collected since they are no longer referenced, but it's unnecessary work in both runtime execution of the constructor and in terms of garbage collection compared to the second code block.

  2. There are unexpected scoping issues in some circumstances. See the Counter example at the end of my answer for an explicit example. If you refer to a local variable of the constructor from the prototype method, then your first example creates a potentially nasty bug in your code.

There are some other (more minor) differences. Your first scheme prohibits the use of the prototype outside the constructor as in:

Filter.prototype.checkProduct.apply(someFilterLikeObject, ...)

And, of course, if someone used:

Object.create(Filter.prototype) 

without running the Filter constructor, that would also create a different result which is probably not as likely since it's reasonable to expect that something that uses the Filter prototype should run the Filter constructor in order to achieve expected results.


From a run-time performance point of view (performance of calling methods on the object), you would be better off with this:

var Filter = function( category, value ){
  this.category = category;
  this.value = value;

  // product is a JSON object
  this.checkProduct = function( product ){
    // run some checks
    return is_match;
  }

};

There are some Javascript "experts" who claim that the memory savings of using the prototype is no longer needed (I watched a video lecture about that a few days ago) so it's time to start using the better performance of methods directly on the object rather than the prototype. I don't know if I'm ready to advocate that myself yet, but it was an interesting point to think about.


The biggest disadvantage of your first method I can think of is that it's really, really easy to make a nasty programming mistake. If you happen to think you can take advantage of the fact that the prototype method can now see local variables of the constructor, you will quickly shoot yourself in the foot as soon as you have more than one instance of your object. Imagine this circumstance:

var Counter = function(initialValue){
  var value = initialValue;

  // product is a JSON object
  Counter.prototype.get = function() {
      return value++;
  }

};

var c1 = new Counter(0);
var c2 = new Counter(10);
console.log(c1.get());    // outputs 10, should output 0

Demonstration of the problem: http://jsfiddle.net/jfriend00/c7natr3d/

This is because, while it looks like the get method forms a closure and has access to the instance variables that are local variables of the constructor, it doesn't work that way in practice. Because all instances share the same prototype object, each new instance of the Counter object creates a new instance of the get function (which has access to the constructor local variables of the just created instance) and assigns it to the prototype, so now all instances have a get method that accesses the local variables of the constructor of the last instance created. It's a programming disaster as this is likely never what was intended and could easily be a head scratcher to figure out what went wrong and why.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
3

While the other answers have focused on the things that are wrong with assigning to the prototype from inside the constructor, I'll focus on your first statement:

Stylistically, I prefer this structure

Probably you like the clean encapsulation that this notation offers - everything that belongs to the class is properly "scoped" to it by the {} block. (of course, the fallacy is that it is scoped to each run of the constructor function).

I suggest you take at the (revealing) module patterns that JavaScript offers. You get a much more explicit structure, standalone constructor declaration, class-scoped private variables, and everything properly encapsulated in a block:

var Filter = (function() {

    function Filter(category, value) { // the constructor
        this.category = category;
        this.value = value;
    }

    // product is a JSON object
    Filter.prototype.checkProduct = function(product) {
        // run some checks
        return is_match;
    };

    return Filter;
}());
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • An IIFE is an inefficient solution for a code style problem, in this case the first and last two lines can be removed and nothing practical is altered and some closures are avoided. Typically it would be used where the function assigned to *Filter.prototype* was different depending on some logic, say a feature test. An alternative is to use a label and block: `Filter: { /* code here */ }`. Or just comments. – RobG Jan 31 '15 at 21:02
  • @RobG: Yeah, the module pattern is unnecessary for this small example. However, it has some other advantages when the code grows; e.g. you could introduce a local alias for `Filter.prototype` to avoid repetition. I would avoid using a block, as a function declaration is syntactically invalid inside of it. – Bergi Jan 31 '15 at 22:34
  • I figured it would become a named function expression, but that may not be correct. It works in every browser I tested—not an exhaustive list, but it included IE 6. – RobG Feb 01 '15 at 08:23
  • @RobG: Yeah, it [maybe works somehow in some browsers](http://kangax.github.io/nfe/#function-declarations-in-blocks). – Bergi Feb 01 '15 at 13:14
2

The first example code kind of misses the purpose of the prototype. You will be recreating checkProduct method for each instance. While it will be defined only on the prototype, and will not consume memory for each instance, it will still take time.

If you wish to encapsulate the class you can check for the method's existence before stating the checkProduct method:

if(!Filter.prototype.checkProduct) {
  Filter.prototype.checkProduct = function( product ){
    // run some checks
    return is_match;
  }
}

There is one more thing you should consider. That anonymous function's closure now has access to all variables inside the constructor, so it might be tempting to access them, but that will lead you down a rabbit hole, as that function will only be privy to a single instance's closure. In your example it will be the last instance, and in my example it will be the first.

Aviv Shaked
  • 762
  • 3
  • 8
1

Biggest disadvantage of your code is closing possibility to override your methods.

If I write:

Filter.prototype.checkProduct = function( product ){
    // run some checks
    return different_result;
}

var a = new Filter(p1,p2);

a.checkProduct(product);

The result will be different than expected as original function will be called, not my.

lujcon
  • 576
  • 4
  • 8
0

In first example Filter prototype is not filled with functions until Filter is invoked at least once. What if somebody tries to inherit Filter prototypically? Using either nodejs'

function ExtendedFilter() {};
util.inherit(ExtendedFilter, Filter);

or Object.create:

function ExtendedFilter() {};
ExtendedFilter.prototype = Object.create(Filter.prototype);

always ends up with empty prototype in prototype chain if forgot or didn't know to invoke Filter first.

Kuba Wyrostek
  • 6,163
  • 1
  • 22
  • 40
  • Given your first point, does this mean the first structure would be better served by using a function declaration versus a function expression? – James Heston Jan 31 '15 at 20:24
  • Well, as soon as you invoke your `ExtendedFilter` constructor it'll invoke `Filter` and the method "magically" appears on your prototype. It would work well, it doesn't matter for inheritance that the prototype is initially empty. – Bergi Jan 31 '15 at 20:29
  • It will not invoke `Filter` automatically, only if programmers decides to when writing `ExtendedFilter` body… (it may be bad practice not to, but still...). I do not think this answer deserves the downvote since OP asked whether this practice could be "causing a fellow developer grief and aggravation" and I do not feel comfortable in JavaScript world with prototypes being empty until first constructor invoke. – Kuba Wyrostek Jan 31 '15 at 20:36
  • Yeah, it is uncomfortable to wait with prototype initialisation until the constructor is invoked (as for example @lujcon has shown), but not for the reason that inheritance won't work. – Bergi Jan 31 '15 at 22:37
0

Just FYI, you cannot do this safely either:

 function Constr(){

    const privateVar = 'this var is private';

    this.__proto__.getPrivateVar = function(){
       return privateVar;
    };

 }

the reason is because Constr.prototype === this.__proto__, so you will have the same misbehavior.

Alexander Mills
  • 90,741
  • 139
  • 482
  • 817