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 2That'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 nowIt 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