Mixing in and Extending JavaScript Getters and Setters
I like to use JavaScript getters and setters. Even though they do not work in IE6-8, we can use them with node.js on server side. There's one issue though — mixing in those methods.
A lot of JavaScript libraries and frameworks have
mixin
helpers, for example jQuery.extend()
, angular.extend()
and _.extend()
. It is simple and powerful,
although if you try to use those methods for extending getters, here's what you'll get:
var sumMixin = {
a: 1,
b: 1,
get sum() {
return this.a + this.b;
}
}
var Foo = function() {};
_.extend(Foo.prototype, sumMixin);
var foo = new Foo();
foo.a = 3;
foo.b = 3;
console.log(foo.sum); // will output 2
That's because after _.extend
execution Foo.prototype.sum
is not a method,
it is actually a result of sumMixin.sum
execution.
We can workaround this issue by assigning prototype directly, although if you want to add other
methods to Foo.prototype
you'll have to make it this way:
var Foo = function() {};
Foo.prototype = sumMixin;
Foo.prototype.bar = function() {};
Foo.prototype._test = 'test';
// And here's a getter :/
Object.defineProperty(Foo.prototype, 'test', {
get: function() {
return this._test;
}
});
var foo = new Foo();
foo.a = 3;
foo.b = 3;
console.log(foo.sum); // will output 6 now
It works, but
- we can apply only one mixin this way
- we lose the sugar we had with
extend()
helpers.
If you still like magic methods and mixins you can tweak your favorite extend()
method
to respect getters and setters like this (based on Underscore):
_.extend = function(obj) {
Array.prototype.slice.call(arguments, 1).forEach(function(source) {
var descriptor, prop;
if (source) {
for (prop in source) {
descriptor = Object.getOwnPropertyDescriptor(source, prop);
Object.defineProperty(obj, prop, descriptor);
}
}
});
return obj;
};
Object.getOwnPropertyDescriptor
and Object.defineProperty
help us copy sum
method description instead of its value to the prototype.
Now mixing in works as expected:
var Foo = function() {};
_.extend(Foo.prototype, sumMixin, {
bar: function() {},
_test: 'test',
get test() {
return this._test;
}
});
var foo = new Foo();
foo.a = 3;
foo.b = 3;
console.log(foo.sum); // will output 6