2011-10-17

A JavaScript class pattern that starts with a function

There are too many JavaScript class patterns and inheritance APIs out there. Here is another one. The twist: Its core construct is a function, not an object literal.

Example usage:

    var MyClass = function () {
        this.count = 0;
    }
    .extends(SuperClass)
    .proto({
        myMethod: function() {
            this.count++;
            // ...
        },
    })
    .class({
        CONSTANT: 123,
    });
We make use of the fact that ECMAScript 5 allows trailing commas in object literals. The class definition starts with a function expression (a function declaration does not allow you to invoke methods) and then invokes methods on it to extend a superclass, add methods and add class properties.

This is the code that makes the above work:

    (function() {
        function copyOwnTo(source, target) {
            Object.getOwnPropertyNames(source).forEach(function(propName) {
                Object.defineProperty(target, propName,
                    Object.getOwnPropertyDescriptor(source, propName));
            });
            return target;
        }

        Function.prototype.extends = function (superClass) {
            var thisProto = Object.create(superClass.prototype);
            // At the very least, we keep the "constructor" property
            // At most, we preserve additions that have already been made
            copyOwnTo(this.prototype, thisProto);
            this.prototype = thisProto;
            return this; // enable chaining
        };

        Function.prototype.proto = function (protoProps) {
            copyOwnTo(protoProps, this.prototype);
            return this; // enable chaining
        }

        Function.prototype.class = function (classProps) {
            copyOwnTo(classProps, this);
            return this; // enable chaining
        }
    }());
Note: This pattern is more a proof of concept than something I would use in practice. But I like that this kind of thing can be done in JavaScript.

Related reading:

  1. Class Definition Pattern Using Object Extension Literals” by Allen Wirfs-Brock [inspiration for this blog post]

2 comments:

Ghasem Kiani said...

The object literal passed to the proto method does not allow defining properties with arbitrary attributes. The following code makes this possible using a new method called desc:

[code]
...

function defOwn(descriptors, target) {
Object.getOwnPropertyNames(descriptors).forEach(function (propName) {
Object.defineProperty(target, propName, descriptors[propName]);
});
return target;
}

Function.prototype.desc = function (descriptors) {
defOwn(descriptors, this.prototype);
return this;
}

...
[/code]

Axel Rauschmayer said...

Interesting idea. Don’t forget that there is Object.defineProperties (which you have reimplemented as defOwn()). In principle you could wrap that method around the argument of proto(), but having an extra method looks better.

Web Analytics