Wednesday, March 16, 2011

Object.defineHybridProperty

Update Yes, I did it: getters and setters for IE < 9 and other browsers


After my early Hooorrayyyy! about compatible IE < 9 getters and setters, I have been experimenting a bit more on how to solve the JSObject to VBVariant and vice-versa assignment and the result was an horrendous monster loads of potential memory leaks and performances implications for the already slow bounce of browsers such IE8, 7, and 6.
Since limitations were also too many, as described in this even earlier attempt from dojo framework, I have realized that VBScript was simply a no-go, or better, probably the wrong answer to the question: how can I have cross-browser getters and setters?

The jQuery Hybrid Answer

Back in 2009, James Padolsey described the jQuery framework as a sort of getters/setters simulator API, comparing the semantic and beauty of non-standard Spidermonkey __defineGetter__ and __defineSetter__, against jQuery coding style, where many "methods" could be considered as getters or setters.

// get the innerHTML of the first node found through the selector
$("#selector").html();

// set the innerHTML of the first(?) node found through the selector
$("#selector").html("
");

As showed above, the jQuery().html method can be considered a friendly answer for a cross browser get/set implementation, and surely much more friendly than what antimatter15 proposed some time ago (Pssss! dude, I could not find your real name in the "About" section ...).

My "due jQuery success, why not!" Proposal

In ES5 everything is so natural and simple, Object.defineProperty and Object.defineProperties work like a charm and IE9 with all other browsers as well. We may decide to use good old __defineGetter/Setter__ as fallback for older Opera, Chrome, Safari, or Firefox, but not for IE since these methods are not supported at all.
However, even using these fallbacks, the descriptor object will result inconsistent because writable, enumerable, and configurable properties won't act as expected.
At this point I have decided to fallback into a "jQuery approach" solution that will behave exactly the same in all old and newer browsers, being still able to use an ES5 like descriptor that in a not that far away future won't even need to be changed at all, it will simply work.

A generic Person.prototype Descriptor



var personDescriptor = {
// requires council notification on change
name: {
get: function () {
return this._name;
},
set: function (name) {
// notify here the council about this change
this._name = name;
}
},
// this property is unfortunately immutable (via public access)
age: {
get: function () {
return this._age;
}
},
// simply a method that occurs once per year
birthday: {
value: function () {
this._age++;
}
}
};


Current ES5 Person "Class" Example



//* ES5 example
function Person(name, age) {
// a new Person in town
this._age = age || 0; // default, just born
this.name = name;
}

Object.defineProperties(Person.prototype, personDescriptor);

var me = new Person("Andrea", 32);
// will throw an error
// me.age = 20;
alert([me.name, me.age]); // Andrea, 32
me.birthday();
alert(me.age); // 33
//*/


Current ES3 Person "Class" Example



//* ES3 example
Person = function Person(name, age) {

// a new Person in town
this._age = age || 0; // default, just born

// we still want to notify the council,
// no direct this._name set
this.name(name);

}

Object.defineHybridProperties(Person.prototype, personDescriptor);

var me = new Person("Andrea", 32);
// nothing will happen
// me.age(20);
alert([me.name(), me.age()]); // Andrea, 32
me.birthday();
alert(me.age()); // 33

// wanna know what they are?
alert(me.name);
alert(me.age);
alert(me.birthday);
//*/

The source code of Object.defineHybridProperty, together with Object.defineHybridProperties, is here and as you can see it's a quite simple and compact piece of code.

Use Cases and DONTS

The function name should be explicit enough, and it's used to define hybrid properties.
Hybrid properties could be confused with methods ... and actually, all hybrid properties that have a get and/or set descriptor, become de-facto an object methods with current constrain: zero arguments to get, 1 single argument to set.
Use cases have been already described, a jQuery like API could use without problems the current approach, making the future ES5 only refactory less painful than whatever other get/set approach.
Indeed, once we know which descriptor is using getters/setters, all we have to do is to remember which property has been made hybrid, and change accordingly each invoke with arguments as assignment, and removing brackets from every other empty invoke.
Please Note I will update later the code in order to properly assign Object.prototype native names such toString so that IE browsers will consider them as well.
Done, it's updated and 409 bytes minified and gzipped, have fun :)

No comments:

Post a Comment