Caution: my English is far from perfect. (Русский тоже не всегда хорош).

Tuesday, 27 September 2011

Private Members in Javascript

It is usually advised to use closures to emulate private class members in javascript. Like this:

function MyClass(param) {

   var privateVar = 1;

   function privateMethod(a) {
      return a + 2;
   }

   this.publicVar = param;

   this.publicMethod = function(x) {
     return x + this.publicVar - privateVar;
   }
}

var my = new MyClass();
my.publicMethod(1);

I find this approach inconvenient for various reasons. Right away I can remember at least 4:

  • Private members are not visible in debugger (FireBug)
  • You can't access private members programmatically (during development I often need to experiment and access private members)
  • Public methods which need access to private data should be defined inside of the closure, we can't add them to the class prototype.
  • In the code private and public members are referred differently: without this for private and with this for public. If I want to make some previously private method public, I need to find all the references and change method() to this.method(). And often the correct this is not available, because we are in some deeper function/closure, where this refers different object (for example we are in a callback of AJAX call). So, making a method public can not always be done mechanically.

We may try to workaround the last issue by defining even public methods as closure-local functions and then publishing them explicitly:

function MyClass(param) {

   // ...

   function publicMethod(x) {
     return x + publicVar - privateVar;
   }

   function publicMethod2(x, y) {
     return publicMethod(x) + y;
   }

  this.publicMethod = publicMethod;
  this.publicMethod2 = publicMethod2;
}

But that way descendant classes can't override the public methods. In short, the closure approach is unsatisfying.

I have an idea which looks for me better than using closures to emulate private members.

The class implementation may have everything accessible via this (i.e. public):

function MyClass(param) {
   this.privateVar = param;
}

MyClass.prototype.privateMethod = function (a) {
      return a + 2;
}

MyClass.prototype.publicMethod = function(x) {
     return x - this.privateVar;
}

var my = new MyClass();
my.privateMethod(2);
my.publicMethod(1);
Then, if I want to restrict class clients from using the private implementation details (and feel that code comments are not enough for that) I can provide them only public interface:
var my = {
   impl: new MyClass(1),
   publicMethod: function (x) {return this.imp.publicMethod(x);}
}

// This is how clients normally use it:
my.publicMethod(1);

// But it is also possible to check the private implementation for debugging and development purposes:
my.impl.privateMethod(2);

This separation of encapsulation from classes resembles Common Lisp.

And the difference between obj.method() and obj.impl.method() is similar to Common Lisp: package-name:func-name for exported symbols and package-name::func-name for not-exported symbols. Client can access private implementation if he wants, but the syntax difference ensures he knows what he is doing.

Depending on the situation, we may create such interface objects manually, or create a little function which wraps any object into an interface:


// Whole the code below is tested, you can run it e.g. in FireBug console.

function as(impl, interface) {
  var wrapper = {impl: impl}; 
  for (prop in interface) {
    wrapper[prop] = makeImplCaller(prop);
  }
  return wrapper;
}

function makeImplCaller(functionName) {
    return function() {
        return this.impl[functionName].apply(this.impl, arguments);
    }
}

// This is the public interface
var Interface = {
  funcA: null, // (a)
  funcB: null, // (a, b)
}

// The implementation of the interface
function Impl (x, y) {
    this.privX = x;
    this.privY = y;
}

Impl.prototype.funcA = function (a) {
    return this.privX + a;
}

Impl.prototype.funcB = function (a, b) {
    return this.privX - a + this.privY - b;
}

// How to provide the inteface to client
var impl = new Impl(5, 10);
var i = as(impl, Interface);

// And client uses it:
i.funcA(1);
i.funcB(7,5);
// inspecting private details
i.impl.privX;
i.impl.privY;

Beware, in some cases the fact that implementation is wrapped into a wrapper might affect the program. If we compare o1 === o2, it might happen that o1 is impl, and 02 is a wrapper, and we get false when true is expected. This is the kind of problem many Java programmers face sometimes (in Java ecosystem various decorators, AOP, proxies, etc. are often used).

I had no time to think further about this approach. For example, in the above code making i instanceof Interface returning true.

But in general I like the idea.

Blog archive