views:

165

answers:

4

Are there any downsides to using a JavaScript "class" with this pattern?

var FooClass = function()
{
  var private = "a private variable";
  this.public = "a public variable";

  var privatefn = function() { ... };
  this.publicfn = function() { ... };
};

var foo = new FooClass();
foo.public = "bar";
foo.publicfn();
+2  A: 

That's good so far, but there's another access level you've left out.

this.publicfn is really a priveleged method as it has access to private members and functions.

To add methods which are public but not priveleged, modify the prototype as follows:

FooClass.prototype.reallypublicfn = function () { ... };

note that this method does not have access to private members of FooClass but it is accessible through any instance of FooClass.

Another way of accomplishing this is returning these methods from the constructor

var FooClass = function()
{
  var private = "a private variable";
  this.public = "a public variable";

  var privatefn = function() { ... };
  this.publicfn = function() { ... };

  return {
    reallypublicfn: function () { ...}
  }
};

var foo = new FooClass();
foo.public = "bar";
foo.publicfn();

Basically, these methods of data hiding help you adhere to traditional OOP techniques. Generally speaking, improving data-hiding and encapsulation in your classes is a good thing. Ensuring low coupling makes it much easier to change things down the road, so publicly exposing as little as possible is really to your benefit.

See https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript for a simple overview and http://www.crockford.com/javascript/private.html for details on how to accomplish these things.

Jonathan Fingland
Your FooClass example is incorrect. The semantics of new are effectively: "var foo = {}; foo.__proto__ = FooClass.prototype; var temp = FooClass.call(temp); if (IsObject(temp)) foo = temp;". This means in your example foo will be the new object "{reallypublicfn: function(){...}}" so it won't have the FooClass prototype or the publicfn function
olliej
+5  A: 

What you're doing in your example isn't the "class" pattern people think of in JS -- typically people are thinking of the more "normal" class model of Java/C#/C++/etc which can be faked with libraries.

Instead your example is actually fairly normal and good JS design, but for completeness i'll discuss behaviour differences you'll see between the private and public "members" you have

var private = "a private variable";
this.public = "a public variable";

Accessing private from within any of your functions will be quite a lot faster than accessing public because the location of private can be determined reasonably well just with a static lookup by the JS engine. Attempts to access public require a lookup, most modern JS engines perform a degree of lookup caching, but it is still more expensive than a simple scoped var access.

var privatefn = function() { ... };
this.publicfn = function() { ... };

The same lookup rules apply to these functions as with the above variable accesses, the only real difference (in your example) is that if your functions are called, say privatefn() vs this.publicfn(), privatefn will always get the global object for this. But also if someone does

f = foo.publicfn;
f();

Then the call to f will have the global object as this but it will be able to modify the private variable.

The more normal way to do public functions however (which resolves the detached public function modifying private members issue) is to put public functions on the prototype, eg.

Foo.prototype.publicfn = function() { ... }

Which forces public functions to not modify private information -- there are some times where this isn't an option, but it's good practice as it also reduces memory use slightly, take:

function Foo1() {
    this.f = function(){ return "foo" };
}

vs

function Foo2() {
}
Foo2.prototype.f = function(){ return "foo" };

In Foo1 you have a copy of the function object for every instance of Foo1 (not all the emory, just the object, eg. new Foo1().f !== new Foo2().f) whereas in Foo2 there is only a single function object.

olliej
+2  A: 

The main downside is that you'll wind up with a copy of publicfn for each instance of FooClass. If you'll be creating a lot of FooClass objects, it would be more efficient to write

FooClass.prototype.publicfn = function() { ... };
edsoverflow
note that those two ways of creating publicfn are not equivalent. one is priveleged and the other is not
Jonathan Fingland
+1  A: 

It depends on your needs and relative performance. Javascript isn't the most type-safe language and isn't very strong with regards to member visibility. Traditionally you can have "private", "public privileged", and "public" visibility within a Javascript type.

You can declare private and public privileged members using:

function FooClass()
{
    var privateVar = 1;
    function privateFn() 
    {
        return privateVar; // etc...
    }
    this.publicVar = 2;
    this.publicFn = function()
    {
       return privateFn();
    }
}

This example uses a function closure, which consists of a function declaration that includes values from the scope where the function is defined. This is acceptable when member visibility is necessary but can lead to overhead. The JavaScript interpreter cannot reuse the privateFn or publicFn definitions for every instantiation since they refer to variables or functions in the outer scope. As a result every instance of FooClass results in additional storage space for privateFn and publicFn. If the type is uses infrequently or in moderation the performance penalty is neglegible. If the type is used very often in the page, or if the page is more of an "AJAX" style where memory isn't freed as frequently since the page is not unloaded then the penalty can be more visible.

An alternative approach is to use prototype members. These are unprivleged public members. Since Javascript is not entirely type-safe and is relatively easy to modify after it's loaded, type safety and member visibility aren't as reliable for controlling access to type internals. For performance reasons, some frameworks like ASP.NET Ajax instead using member naming to infer visibility rules. For example, the same type in ASP.NET Ajax might look like:

function FooClass2()
{
    this._privateVar = 1;
    this.publicVar = 2;
}
FooClass2.prototype = 
{
    _privateFn : function() 
    { 
        return this._privateVar; 
    },
    publicFn : function() 
    {
        return this._privateFn(); 
    }
}
FooClass2.registerClass("FooClass2");

In this case the private scoped members are private in name only, the "_" prefix is considered to mean a private member variable. It has the downside of preventing the member from being truly private, but the upside of allowing in-memory patching of the object. The other main benefit is that all functions are created once by the interpreter and engine and reused over and over for the type. The "this" keyword then refers to the instance of the type even though the function reference itself is the same.

One way to see the difference in action is to try this with both types (if you don't have ASP.NET Ajax, you can ignore the last line in FooClass2 that calls registerClass())

var fooA = new FooClass(), fooB = new FooClass();
alert(fooA.publicFn===fooB.publicFn); // false

var foo2A = new FooClass2(), foo2B = new FooClass2();
alert(foo2A.publicFn===foo2B.publicFn); // true

So its a matter of type safety and member visibility vs. performance and the ability to patch in memory