Sunday, March 25, 2012

What I learned from THREE.js: new way to encapsulate

Recently I've had the oportunity to work extensively with THREE.js, a WebGL framework primarily authored by Mr. Doob. This library simplifies the use of the powerful WebGL graphics engine. Not only is this library an excellent resource for WebGL, it also introduced me to some new techniques for JavaScript encapsulation and inheritance. This article presents a technique for simplifying method definition syntax.

Restructuring Class Implementation

The common method for adding methods to an object is to add the method names directly to the prototype.

var ClassName = function(){};
ClassName.prototype.methodName = function(){};

This works, but is a little verbose, and requires a lot of find/replace when overriding inherited methods. The clases in THREE.js avoid this repetition by defining instance methods during object construction. It looks like this.

var ClassName = function(){
        this.methodName = function(){};
};

The benefits are twofold. The syntax for method definition and overriding has been simplified. Also, Using the constructor for encapsulation obviates the use of closures to obfuscate private methods (as demonstrated in this previous post), further simplifying syntax.

Consider the following two classes.

/*
Class: ClassA
*/
var ClassA = function () {
        //add functions to object inside of constructor, simpler syntax
        this.foo = function () {
            console.log("parent foo");
            privateFoo.call(this);
        };
        //private methods also defined inside constructor
        function privateFoo() {
            console.log("parent private foo");
        }
    };

/*
Class: classB
inherits from classA.
*/
var ClassB = function () {
        this.foo = function () {
            console.log("child foo");
            ClassB.prototype.foo.call(this); //calling parent method
        };

        this.bar = function () {
            console.log("child bar");
        };
    };
ClassB.prototype = new ClassA();

var b = new ClassB();
b.bar(); //child bar
b.foo(); //child foo, parent foo, parent private foo

This is significantly more concise, and worth investigating further.

Issues

An obvious fault is that to call the parent method you have to access the child's prototype. This is conceptually confusing and a regression from the previously outlined method, where the parent prototype would be accessed (ie. ClassA.foo.call(this) ).

There is also a potential performance issue. It is possible that creating a new definition of instance methods at runtime will have an adverse affect on either memory footprint or speed. This will have to be tested.

Conclusion

It is to early to tell if I will start using this particular encapsulation scheme. The parent method mapping is a little strange, and the performance issue is something I'll have to investigate further. I will post any findings.

No comments:

Post a Comment