Pensando en programar

Why is it so ugly mixing getters/setters and prototypes?

Question: The question, to be more precise, as was asked by an anonymous friend, is: “Why is it so ugly using as a prototype an object that has getters or setters?”.

Iron(II) oxide

Well, ugliness is sometimes subjective, of course. But not in this case. It is ugly. Let´s try to understand the question first, though.

The visible problem is this:

When dealing with the prototype chain we already know how property access works. Everybody knows 1): If we try to read obj.property (let v = obj.property;) then property is looked up on obj and if it´s not found there, it is looked up on obj´s prototype, and if not found there, on the prototype´s prototype and so on until the end of the chain is reached. On the other hand, when we try to write obj.property (obj.property = v;) the prototype chain doesn´t come into play at all: If property is not found on obj, then it is simply added to obj itself and the value assigned.

This is the usual and known behaviour of the prototype chain from the times of ECMAScript 3. This is nothing new.

Then, there´s getters and setters. This a bit more recent but I also hope it´s known by everyone. It just means we can define a property on an object as a setter or getter:

let obj = {
    get property() { return "blah"; }
};

And this would allow us to write…

let v = obj.property;

…with a syntax that pretends property was just a static property while we´re actually calling a function. In a similar manner we can declare a setter with the keyword set 2) and it will be executed when we assign a value:

let obj = {
    set property(v) { console.log(v); }
};
obj.property = "This and that";

Again, this, while a bit more recent -we´ll later see exactly when it was added 3)-, is also the usual behaviour for the concept get/setters. The concept exists and is used in other languages too. So, up to here, everything´s fine.

But… what happens when we put together this two apparently simple ideas? Here´s where the ugliness creeps in. Because when get/setters were added the lookup rules for the prototype chain had to be slightly modified. With get/setters, the rules are a bit less simple:

let monkey = {
    get food() { return "I eat bananas"; }
};
let sophisticatedMonkey = Object.create(monkey);

console.log(sophisticatedMonkey.food); // Oh, no, bananas are so unsophisticated
sophisticatedMonkey.food = () => "I eat grapes and cheese"; // Let's evolve and be civilized

console.log(sophisticatedMonkey.food); // Sorry, monkey. You can't escape your nature!
console.log(sophisticatedMonkey.hasOwnProperty('food')); // -> false! false? Yes, false.

What happens is that when trying to write sophisticatedMonkey.food even though it doesn´t have such a property, there is indeed one on its prototype, and also it turns out to be a property defined as non-writeable 4). So, then, instead of adding a property food to sophisticatedMonkey, the existence of a non-writeable property with that same name on some point of the prototype chain blocks the assignment 5).

Ah, but… we might think… no, adding a setter (or making the property writeable, tato mota) would not really fix things. What it would do is that our assignment attempt would execute that setter, but it would not allow us to assign that property on sophisticatedMonkey.

The anonymous question was also accompanied by some comments on the possibility of “solving” this by using Object.defineProperty, which is, sadly, just as ugly… or even uglier, because what we would be actually doing is create another property on sophisticatedMonkey coincidentally called food. As a solution it is not just cumbersome but it also goes from ugly to horrifying.

What's wrong with disgusting problems?

Or did I mean discussing? No, I meant ignoring them. What´s wrong with ignoring problems? Or at least trying to avoid them? IT seems that a fair number of people choose that option and decides on not using get/setters at all because of the cost it implies.

The thing is the getaway is not always clean and, well, as a general concept, get/setters are sometimes quite practical. In any case, it´s clear they do have a -generally- high cost and not only in elegance. Sometimes they also have a performance cost.

But all of this only explains a quite superficial why. The why of the ugliness itself, the why of the resulting situation. It might be helpful to understand why it was done this way. Sadly, we get into a territory that is a bit difficult to explore, and even more, difficult to analyse.

It´s interesting to have a bit of context first. I mentioned getters and setters officially made it into the specification for ECMAScript 5, which means December 2009. Nevertheless, we need to point out that, actually, they were added to JavaScript almost ten years before, around 2000. And it is also relevant understanding to some extent 6) what happened in that period.

This is before ES5 but also before the failure of ES4. It´s even before the attempt of ES4. It´s before even the first version of Firefox or the existence of Mozilla. To be more precise, it´s the period of Internet Explorer´s clear dominance and this, leaving other things aside, means for JavaScript that it´s a period were mostly two different JavaScripts exist: One is what we write professionally, which is, yes, quite horrible, but as it has to work on IE4/5 which everyone uses, it is limited to basically JavaScript 1.2/1.3. And then there´s SpiderMonkey, where Brendan Eich is slowly adding, without much interest on standardizing, new capabilities, such as getters and setters.

And the problem is not so much the standardization. But what I think most characterizes this moment in the development of the language is that those new capabilities that get added aren´t really used by anyone, because they are only available in Netscape (later Firefox). And so, there´s no response. The development that is done in those years 2000-2003 in JavaScript completely ignores these new options. No problems arise on those capabilities and not because they don´t exist but because nobody uses them.

Much later, a lot later, John Resig was writing this piece on getters and setters. And he speaks about how in 2007 it was already an option using them because finally they were supported not only on Firefox but *gasp* also on Safari and Opera. If you look at 2007 on the graph linked above, this means an impressive 25-30% 7). 7 years later, more or less at the same time of the ES4 attempt.

I personally don´t remember that Resig´s enthusiasm on getters and setters was shared by a large part of the community. Yes, I do remember some conversations on some mailing list, but little more. Nothing against them either, just that they got little attention. I know that my memory is not always the best, so let´s admit they got at least a certain, small interest. A couple of years of fights and discussions later, ES5 would come and, along with mostly just fixes and adjustments, getters and setters would be discretely introduced into the spec as an official feature.

Meanwhile, on the other side of town...

The problem with the lack of use and feedback form programmers is accentuated for an additional reason. It´s not only that some features get no usage for lack of IE support. I´m afraid extended knowledge of JavaScript in the period (2000-2009) is poor, engine performance is fragile and appropriate tools are just beginning to appear 8). And let´s remember that one of the most charming moves from jQuery in 2006 was to alias jQuery.prototype as jQuery.fn so that people would get to write plug-ins without the prevailing fear of that prototype thing.

It´s admittedly speculative on my part, of course, but putting together all these ideas, I guess that only long after the mechanics of interaction between get/setters and prototypes were already solidified in the language, is when they both got the be used more extensively, and only then when, consequently, those small 9) details started to surface. What´s more, I suspect that in a fair number of cases, people may have indeed found out about this in those years (at least in the final part, 2006-2009) and may have simply decided to avoid the problem by doing things in a different way.

I know it´s pure speculation, yes, but I also notice that it is precisely in those years, 2005 to 2009, when the attempts to build class systems, inheritance systems, extension systems over JavaScript´s prototype chain, were flourishing everywhere. There was no library, blog, mailing list, big or small, popular or not so much, where something of this kind wasn´t discussed or presented. And most attempts were… pretty baroque, rococó or even churrigueresque. I don´t think it´s so outlandish to assume at least some lack of knowledge or interest.

Oh, well, I think that as original motivation for that ugliness I would pick that lack of usage until it was too late.

1)
Or at least I will innocently assume that everyone knows about it
2)
There is another more explicit syntax to define getters and setters using Object.defineProperty; the result is more or less the same but the syntax is more tiring to write. Even more! There´s also Object.prototype.__defineGetter__ and Object.prototype.__defineSetter__ , but these are decprecated to we´ll happily ignore them
3)
Officially get/setters made it into the specification for ES5, but… no, let´s not get ahead of ourselves xD
4)
It only has a getter but not a setter
5)
and it does so silently, with no error or indication given
6)
Really, I don´t like having to write about the history of JavaScript :( but we need some small pieces
7)
which is impressive, yes, but it still means you can´t use it professionally in most environments
8)
Firebug is from 2006. jQuery too.
9)
or not so small