Monday, December 06, 2010

Javascript: Defining Classes with Closures

Over the weekend I watched Douglas Crockford’s five part series on Javascript, Crockford on Javascript. Probably one of the best video introductions to a programming language I have ever seen.

The section I enjoyed the most was part 3, Function the Ultimate, especially the section where he discusses object creation. I’ve always disliked prototype based inheritance. It’s clumsy, awkward and lacks encapsulation. Crockford shows a much nicer object creation strategy based on closures, which looks something like this:

var animalsApp = (function(){

var new_animal = function(name) {
var animal = {};

animal.sayHello = function() {
return "Hello, my name is " + name;
}
return animal;
}

var new_dog = function(name) {
var dog = new_animal(name);

dog.bark = function() {
return "woof";
}
return dog;
}

var new_cat = function(name) {
var cat = new_animal(name);

cat.meow = function() {
return "eeooowww";
}
return cat;
}

return {
main: function(){
var dog = new_dog("rover");

console.log(dog.sayHello());
console.log(dog.bark());

var cat = new_cat("tom");

console.log(cat.sayHello());
console.log(cat.meow());
}
};
}());

animalsApp.main();

Using this technique we can create properly encapsulated objects. In the example above, our animalsApp only exposes a single function, main, new_animal, new_dog and new_cat are all private. The class definitions are very clear, any variables or functions defined in the constructor body are private and we only expose members that we explicitly attach to the object (dog.bark = function(){}).

You can run this example using node:

$ node closureclass.js
Hello, my name is rover
woof
Hello, my name is tom
eeooowww

10 comments:

David Arno said...

Whilst this is a nice JavaScript hack to create encapsulated objects, it would have been sooooo much easier for everyone if the powers that be had adopted ECMAScript 4. Then JavaScript would have real classes...

Mike Hadlow said...

I'm not sure. Some (Crockford for example) would argue that you simply don't need a definition for Class when you have first class functions.

Anonymous said...

http://closure-library.googlecode.com/svn/docs/index.html

j.rebhan said...

But how to do polymorphism / overwrite an existing function with this pattern?

fschwiet said...

I like this approach, but since it redefines every function for every instance I would not use it for objects that have many many instances.

Have you checked out JasmineBDD for unit testing? I highly recommend it https://github.com/pivotal/jasmine

Justin Johnson said...

Prototypal object definition doesn't entirely lack encapsulation. It encapsulates data, but doesn't restrict access. However, you can still achieve this with closures while still creating inheritable objects. IMO, this is decorating when you should be inheriting.

Also, this doesn't address the (supposed) access restriction short comings of JavaScript since everything inside of main() still has uncontrolled access to the members of animal/dog/cat. The only thing you've done is blocked the top most level (the context that executes `animalsApp.main();`) from accessing anything in the defined animal objects while still allowing the execution context within `main` to access and alter any property on those objects that it wants. I don't see how this is beneficial as more than an exercise.

Mike Hadlow said...

@j.rebhan,
Crockford shows how to do this in the video, it's pretty simple, to override you just redefine the method for the new class.

@fschwiet,
That's a very good point. You could write a constructor function that only created the methods once, but it would be a little more complicated.

@Justin,
No, the init function cannot access the 'name' of dog or cat. Also any variable or function defined within the constructor function will be private.

Scott Koon said...

j.rebhan: The only method you can override is main() and you can do this only on instances of the object Mike is creating using the standard method of overriding a JS member. object["member name"] = whatever.

Justin: Main can not access the members of dog,cat etc... declared with the var keyword inside of the function. Of course this, like any example code, is trivial. In my script loading library, you can see this a little more clearly. The only method anything outside of my function has access to is the "bootstrap" function. All of the other functions and variables declared inside of it are protected.

http://bitbucket.org/scott_koon/bootstrap/src/b8e11fb4f982/bootstrap.js

Scott Koon said...

Justin: HA my mistake, they can access the members of cat dog, but only because the function is returning them. If the function were to wrap them with an accessor function then they could only access them via that function.

Anonymous said...

nice!