views:

56

answers:

3

Recently i saw the following code that creates a class in javascript:

var Model.Foo = function(){
  // private stuff
  var a, b;

  // public properties
  this.attr1 = '';
  this.attr2 = '';

  if(typeof Model.Foo._init === 'undefined'){
    Model.Foo.prototype = {
      func1 : function(){ //...},
      func2 : function(){ //... },
      //other prototype functions
    }
  }
  Model.Foo._init = true;
}

// Instantiate and use the class as follows:
var foo = new Model.Foo(); foo.func1();

I guess the _init variable is used to make sure we don't define the prototypes again. Also, i feel the code is more readable since i am placing everything in a function block (so in oop-speak, all attributes and methods are in one place). Do you see any issues with the code above? Any pitfalls of using this pattern if i need to create lots of classes in a big project?

+2  A: 

This is a weird Javascript pattern that I would never use to develop object-oriented JS code. For one thing, Model.Foo._init === 'undefined' never evaluates to true if Model.Foo._init is anything but the string 'undefined'; therefore, the code

Model.Foo.prototype = {
    func1 : function(){ /* ... */},
    func2 : function(){ /* ... */},
    //other prototype functions
}

will not run unless that condition holds true. (Perhaps the author meant to add a typeof, as in typeof Model.Foo._init === 'undefined'? I don't know.)

Addressing your concern about "[making] sure we don't define the prototypes again", this is already achieved with:

Model.Foo = function() {
    // private stuff
    var a, b;

    // public properties
    this.attr1 = '';
    this.attr2 = '';
};

Model.Foo.prototype = {
    func1 : function() { /* ... */},
    func2 : function() { /* ... */}
    //,other prototype functions
};

// Instantiate and use the class as follows:
var foo = new Model.Foo();
foo.func1();

which is along the lines of what I recommend if you aren't using a framework.

Basically, the answer to your question is: if you use this non-standard pattern for development, then other programmers, maybe even yourself a few months later, will find it difficult to extend and work with.

Daniel Trebbien
yes you're right, its typeof....my bad
fenderplayer
what if you wanted to use some private stuff in a prototype function?
fenderplayer
If you wanted a method `func` that alerts the value of `a`, then you could use something like http://pastebin.com/wSff15cH . Notice how line 6 sets `this.func` (not `Model.Foo.prototype.func`) to a function object. Public properties can be methods too, as the example demonstrates. However, keep in mind that the definition of `func` is no longer the same `Function` object for all `Model.Foo` instances. Also, values of all local variables as well as arguments to the constructor will not be GC'd. Therefore, if you use many instances of `Model.Foo` in your code, then you will use a lot of memory.
Daniel Trebbien
I should clarify that values of all local variables and arguments to the constructor will not be GC'd (actually, be eligible for garbage collection) until the corresponding `Model.Foo` instance is also eligible for GC.
Daniel Trebbien
A: 

A few things stand out as potentially troublesome:

  1. Foo.prototype is set to a simple object, which can't extend anything using this pattern.
  2. Where is the "private stuff" used? Every time you create a new object, you create new private variables, which seemingly can only be used in the functions defined in Foo.prototype, which should only be run once.

It's kind of a mess, and there are details/examples all over the web of better was to do this.

z5h
+1  A: 

It just seems unnecessarily complex. You need to be disciplined to not use any parameters or local variables of Model.Foo in the implementation of the prototype extension. Its odd to overwrite the entire .prototype object and not just add individual members as well. Why not just do this the normal way?

var Model.Foo = function(){
  // private stuff
  var a, b;

  // public properties
  this.attr1 = '';
  this.attr2 = '';
}

Model.Foo.prototype.func1 = function(){ //...};
Model.Foo.prototype.func2 = function(){ //... };

alternate allowing per-instance member variables private

var Model.Foo = function(){
  // private stuff
  var a, b;

  // public properties
  this.attr1 = '';
  this.attr2 = '';

  this.func1 = function(){ //...};
  this.func2 = function(){ //... };
}
Frank Schwieterman
So, i thought it would be easier to use the private attributes in the prototype functions (with closure). How would you suggest using the private atuff? Something like 'this.func(){ alert(a);}' inside the class function? Why would you not use private attr in prototype functions?
fenderplayer
I would not use private attributes or parameters to the constructor in the prototype method because they are special to the instance being created, while the prototype methods will apply to all instances. Then the first call to Model.Foo() would have some unique effect, seems hard to maintain.
Frank Schwieterman
With Javascript, I don't really hide privates. I forget that pushing the prototype'd members out of the function block, disallowing private variables. I decided I can live with that. If you can't live with that, you have to put the functions inside the constructor. But don't attach them to the prototype as you are because then those private variables are no longer per-instance variables but rather shared across instances.
Frank Schwieterman
Going back to how I live without privates... I prefix them with '_' so when I review code I can see if someone is inappropriately calling a private. More importantly though, write unit tests for everything.
Frank Schwieterman
"private variables are no longer per-instance variables but rather shared across instances" - i understand now. So my example makes sense if i need to use some private attr to store some kind of reference that will be commonly used by all instances? But in any case, you would discourage using this technique?
fenderplayer
I put a 2nd form that will work as you ask, though its going to redefine every function rather than use the prototype. This typically isn't a problem and I don't recall why I stopped coding classes in the 2nd way. I think it was just a hassle with my text editor's autoformat.
Frank Schwieterman
In addition to Frank's note about how the second form typically does not solve a problem, the second form could also introduce memory leaks. See my comments to my answer ( http://stackoverflow.com/questions/2908692/doubt-about-a-particular-pattern-of-javascript-class-definition/2908791#2908791 ).
Daniel Trebbien
I wouldn't say its a memory leak per se, but it is wasteful.
Frank Schwieterman