Improve this Doc

Montage Objects

MontageJS provides a thin wrapper over the JavaScript object model. Types are represented by constructor functions. Constructor functions have a prototype. The prototype has a constructor. instanceof and new work as you would expect.

For example, following A and B examples are equivalent:

Example A: JavaScript (ECMAScript 5)

function Penguin() {
    Bird.call(this);
}

Penguin.prototype = Object.create(Bird.prototype);

Penguin.prototype.constructor = Penguin;

Penguin.prototype.fly = function () {
    return Bird.prototype.fly.call(this);
};

Object.defineProperty(Penguin.prototype, "habitat", {
    get: function () {
        return this._habitat;
    },
    set: function (value) {
        this._habitat = value;
    }
});

Penguin.staticMethod = function () {};

Example B: Montage Framework

var Penguin = Bird.specialize({
    constructor: {
        value: function Penguin() {
            this.super();
        }
    },
    fly: {
        value: function () {
            return this.super();
        }
    },
    _habitat: {value: null},
    habitat: {
        get: function () {
            return this._habitat;
        },
        set: function (value) {
            this._habitat = value;
        }
    }
}, {
    staticMethod: {
        value: function () {}
    }
});

The Montage constructor has a specialize method that accepts ECMAScript 5 property descriptors for the new prototype and another optional set of descriptors for properties of its constructor. It uses Object.create to extend the parent’s prototype, and Object.defineProperty to apply the property descriptors. For the most part, this just provides a convenient and error-resistant way to declare new types, reinforcing the existing JavaScript conventions.

Montage Methods

However, MontageJS does provide some additional features. Within any Montage method, super(...args) will call the eponymous method of the parent prototype. Likewise, super() within a getter will get a property according to the parent prototype, and super(value) within a setter will set a property according to the parent prototype.

In this example, the Type implements an id getter, where identifiers are granted in the order of first access. Subtype overrides the identifier property such that the identifier is a string with an underscore prefix.

var ids = new WeakMap();
var nextId = 0;
var Type = Montage.specialize({
    id: {
        get: function () {
            if (!ids.has(this)) {
                ids.set(this, nextId++);
            }
            return ids.get(this);
        }
    }
});

var Subtype = Type.specialize({
    id: {
        get: function () {
            return "_" + this.super();
        }
    }
});

Montage also supports a small number of modifications to the ES5 property-descriptor. Montage alters the defaults for writable and configurable properties are both writable and configurable unless you specify otherwise. In general properties continue are enumerable by default. The default is changed for properties with:

  • Names that start with _ - will not be enumerable
  • Value is a function (methods)

Please see Montage.defineProperty API for more info.

Extending the JavaScript Object Model

Perhaps the most subtle and interesting way that Montage.specialize extends the JavaScript object model is that it causes constructor functions to inherit from their parent constructor, in parallel the prototype chain. This makes it possible to use or override Montage.specialize, defineProperties, and defineProperty for subtrees of your object model. Montage implements specialize and defineProperties such that an overridden defineProperty is sufficient to specialize the property descriptor protocol for all descendent types. Overriding specialize gives you a hook to decorate the constructor for all descendent types.

var Type = Montage.specialize({
}, {
    specialize: {
        value: function () {
            return this.super();
        }
    },
    defineProperty: {
        value: function (object, descriptor) {
            return this.super(object, descriptor);
        }
    }
});

For debugging, it is best to give your constructor a name. Montage.specialize permits a specialization to provide the constructor among the prototype property descriptors. It lifts this property out instead of providing a default, anonymous constructor function then goes on to set up its prototype and inheritance chain as normal.

var Type = Montage.specialize({
    constructor: {
        value: function Type() {
            this.super();
        }
    }
});

var Subtype = Type.specialize({
    constructor: {
        value: function Subtype() {
            return this.super();
        }
    }
});