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 any custom methods with the same names on other objects.
The mixinObservable
function also copies only functions. If this check was not made and someone had observed the observableMethods
object itself then the observableMethods.observes
array would be copied as part of every mixin and observers would be shared by some observable objects (e.g. observableMethods
and Person.prototype
in the example above.)
A simpler mixinObservable
method could be the following but this could be more difficult to maintain. If more methods are added to observableMethods
then they need to be explicitly listed in mixinObservable
.
function mixinObservable(sink) {
sink.observe = observableMethods.observe;
sink.notify = observableMethods.notify;
}
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); // optional depending what you want
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.
Comments
Have something to write? Comment on this article.
Hello Peter, thanks for another great read.
Could you expand on “optional depending what you want” comment? When would I want to observe the prototype?
Misha,
Generally I’m fumbling around with the philosophical idea that the prototype object should be a complete, fully-formed object useful in its own right. I think of Plato and his idea that a prototype is the ideal object from which all other objects are cloned. The perfect horse.
I can imagine the Observable.prototype
object being accessed directly and used as a pub/sub object. More observable objects can be created by the Observable
constructor with the global pub/sub object as the prototype of each object produced.
This is contrary to the common JavaScript situation where the prototype is a partial object with some missing pieces. The horse with no legs. Only the objects inheriting from the prototype are complete.
Wow, Peter, this is a very interesting angle to look at it. I was always thinking of and treating prototype
like a piece of machinery.
Funnily, the wiki article on Plato’s idealism is referring to an “ideal” Tree with a capital, just like we do with the constructors :-)
Thanks for inspiration.
Although I like this approach, I’m bothered by something here, and it seems fairly fundamental. This technique requires you to both mix the Observable in with the Person prototype and to update the Person constructor so that the required list of observers is created for each instance. That feels wrong.
I really want to do something like this:
Observable.mixinTo(Person.prototype);
And have it handle both of those chores.
I was thinking about this problem last week, and I have tried another technique which seems to do that, at a cost of each Person having copies of the reference to each Observable method. (It doesn’t copy the functions, only the references.) It does this by creating a temporary API on the constructor with functions that will, when called on an instance, call the necessary initialization and then place references to the real API on the instance.
I wrote a generic function that can create mixins from an API and an initialization function:
Observable.mixinTo = createMixinFunction(Observable.prototype, Observable);
Here the API is simply the entire prototype, and the initialization is the constructor, but they can be pretty much arbitrary. This function would then be used like this:
Observable.mixinTo(Person.prototype);
And the Person constructor would not have to call Observable on the instance. That strikes me as much cleaner.
I’m planning to write this up as an article, but thought I’d respond here, since this is what spurred me to take what I’d scratched out on paper on a bus ride and test it. You can find a first draft of the function at http://scott.sauyet.com/Javascript/Test/Mixin/
Thanks for posting this interesting information.
@Angus
I use the same approach as yours. Only in this case, I would share the functions instead of creating them every time mixin is invoked.
For example:
var observableMethods = function() {
this.observers = [];
this.observe = observableMethods.observe;
this.notify = observableMethods.notify;
};
observableMethods.observe = function(observer) {
this.observers.push(observer);
};
observableMethods.notify = function(data) {
for (var i=0, ilen=this.observers.length; i<ilen; i++) {
this.observers[i](data);
}
};
Or, alternatively, using closure to store observe/notify.
Have something to write? Comment on this article.
As usual, an excellent and enlightening article Peter.
I like the way you use 'call' to add observable methods to both Observable prototype (in one example) and each person instance (in another). Call and apply work brilliantly with dynamic this binding (one reason why I am worried about fat arrow syntax and the way its hard-lexical-binding will declaw call and apply). By the way - are you into 'this' suddenly? :-)
The two examples I cite illustrate the principal of my favorite mixin strategy - functional mixins.
The beauty of this approach is that you get complete, procedural control over the mixin process, by invoking the mixin source as a script, rather than copying. Now we can assign this.observers directly to the target object from within our mixin script so there is no need for hasOwnProperty checks and the mixed in functions become more nimble. You can also, optionally, pass arguments to customize the mixin script.
Of course there is extra overhead in invoking the mixed in functions during the definition process, but this is a one time initialization expense.
Here are your examples using my mixin strategy (also in JS fiddle here)