Wednesday, May 2, 2012

Mixins and Constructor Functions

Mixins and Constructor Functions:
JavaScript allows programmers to take properties from one object and mix them into another object. There are several ways to accomplish this mixing and a few of them are explored here.



Observable Mixin



Here is a simple example of the observer pattern that can be mixed into other objects.

var observableMethods = {
observe: function(observer) {
if (!this.hasOwnProperty('observers')) {
this.observers = [];
}
this.observers.push(observer);
},
notify: function(data) {
if (this.hasOwnProperty('observers')) {
for (var i=0, ilen=this.observers.length; i<ilen; i++) {
this.observers[i](data);
}
}
}
};


It is possible to use the observableMethods function as a observable itself.

observableMethods.observe(function() {
alert('hi');
});
observableMethods.notify();


A little mixin function to make other things observable.

function mixinObservable(sink) {
for (var p in observableMethods) {
if (observableMethods.hasOwnProperty(p) &&
typeof observableMethods[p] === 'function') {
sink[p] = observableMethods[p];
}
}
}


We can mixin to an person created with an object literal.

var person = {
name: 'Steve',
setName: function(name) {
var oldName = this.name;
this.name = name;
this.notify({oldName: oldName, newName: this.name});
}
};
mixinObservable(person);
person.observe(function(data) {
alert(data.oldName + ' was renamed to ' + data.newName);
});
person.setName('Sarah');


Alternately we write a constructor function for observable people that we can rename.

function Person(name) {
this.setName(name);
};

mixinObservable(Person.prototype);

Person.prototype.setName = function(name) {
var oldName = this.name;
this.name = name;
this.notify({oldName:oldName, newName:this.name});
};


We can then make a person and manipulate it.

var person = new Person('Steve');
person.observe(function(data) {
alert(data.oldName + ' was renamed to ' + data.newName);
});
person.setName('Sarah');


In all of the above code, the three uses of hasOwnProperty are critical to understand.

The first two uses of hasOwnProperty in observe and notify ensure that the object into which the methods have been mixed, will have its own set observers and not share some set of observers with any other object. The unfortunate part is that these checks run every time the observe and notify methods are called. This is inefficient and something we want to fix.

The third use of hasOwnProperty in mixinObservable means that only properties directly on observableMethods will be mixed into other objects. This is important because if we do not use hasOwnProperty then we copy all of the enumerable Object.prototype properties which is either wasteful or will overwrite the custom methods with the same names on other objects.



Fixing the Inefficiency



The inefficiency in observe and notify can be fixed by making a constructor function for Observable objects.

function Observable() {
this.observers = [];
}
Observable.prototype.observe = function(observer) {
this.observers.push(observer);
};
Observable.prototype.notify = function(data) {
for (var i=0, ilen=this.observers.length; i<ilen; i++) {
this.observers[i](data);
}
};
Observable.call(Observable.prototype);


Now the observe and notify methods of an observable function are more efficient as we know the observers property was created when the Observable constructor function ran.

The last line, Observable.call(Observable.prototype);, is a bit of an unusual one but it makes it possible to observe Observable.prototype just like observableMethods.

A new mixin function.

function mixinObservable(sink) {
for (var p in Observable.prototype) {
if (Observable.prototype.hasOwnProperty(p) &&
typeof Observable.prototype[p] === 'function') {
sink[p] = Observable.prototype[p];
}
}
Observable.call(sink); // optional depending what you want
}


Mixing into a person created with an object literal just like it was done above. The last line of this new mixinObservable insures the observers property is created.

var person = {
name: 'Steve',
setName: function(name) {
var oldName = this.name;
this.name = name;
this.notify({oldName: oldName, newName: this.name});
}
};
mixinObservable(person);
person.observe(function(data) {
alert(data.oldName + ' was renamed to ' + data.newName);
});
person.setName('Sarah');


The trick comes when we create a Person constructor function.

function Person(name) {
Observable.call(this);
this.setName(name);
};

mixinObservable(Person.prototype);

Person.prototype.setName = function(name) {
var oldName = this.name;
this.name = name;
this.notify({oldName:oldName, newName:this.name});
};


The first line of the constructor function, Observable.call(this);, ensures that each Person object has its own set of observers. Without this call, all the people will share the same list of observers which is the set of observers on the Person.prototype object. If this makes you squint then it is well worth the effort to think about it until it is clear why.

To use some class vocabulary, the first line of the constructor function can be thought of as a super call and that the Person class inherits from the Observable class. Some JavaScript diehards cringe at the mention of this vocabulary but I think the comparison is worth consideration.



Multiple Mixins



Multiple mixins follow the same pattern.

function Person(name) {
Observable.call(this);
Common.call(this);
this.setName(name);
}

mixinObservable(Person.prototype);
mixinCommon(Person.prototype);

// By coincidence mixinCommon also added a notify method which
// clobbered the method of the same name added by mixinObservable.
// Fix this problem making appropriate decisions about how
// to call both.
Person.prototype.notify = function() {
Common.prototype.notify.call(this);
Observable.prototype.notify.apply(this, arguments);
};
// ...




Prototype Chaining vs. Mixins



There is one primary difference between prototype chaining and mixins. For example,

function Person(name) {
Observable.call(this);
Base.call(this);
this.setName(name);
}

// chain prototypes so that all Person objects
// inherit from Base.prototype.
Person.prototype = Object.create(Base.prototype);
Person.prototype.constructor = Person;
Base.call(Person.prototype); // optional depending what you want

mixinObservable(Person.prototype);


Now if we add methods to both Base.prototype and Observable.prototype after the above code has executed. Only the method added to Base.prototype will be added to Person objects.

Base.prototype.getId = function() {/*...*/};
Observable.prototype.getObservers = function() {/*...*/};

var person = new Person('Steve');
person.getId(); // ok
person.getObservers(); // error: getObservers not defined


Enjoy your mixins.

No comments:

Post a Comment

Thank's!