Friday, February 20th, 2009

Comparison of JavaScript Inheritance Mechanisms; Proposal

Category: JavaScript

Andrea Giammarchi took the time to compare the JavaScript inheritance mechanisms in base2, Dojo, jsClass, MooTools, Prototype, and YUI and illustrates the same scenario with all of those frameworks. Handy indeed!

He concludes with a proposal:

WebReflection Proposal
After an analysis like this one, how could I skip my “all the best from others without troubles” proposal?
These are my considerations:

* The fastest way to use a parent method is an explicit call

* The best way to perform above step is via a link able to remove explicit dependencies (read: MyParentClassName instead of this.constructor.superclass). In this way methods could be easily transported from a prototype to another one without problems (less memory usage and less code to maintain)

* The this.parent() solution is the cleanest one and generally speaking more close to our concept of “Object Oriented JavaScript”. It is portable, it is meaningful, it is shorter than an explicit call with or without a link but it is not that fast to execute.

* Using best practices to cache all we need, create a quick wrapper, resolving hidden methods problems, will not bring us to native or YUI performances, but at least in a good scenario: fast enough to guarantee performances over code elegance and readability

So here we are with my proposal:

* Concept: parent in prototype but changed runtime for hierarchy purpose only if necessary (read: only for overrides). This will allows us to call parent directly in the constructor, as example, so we won’t have double calls for each instance. At the same time, this implementation lets us call explicitly a parent method from those whose were not inherited.

* Troubles: performances are the best but still far from native one.

Here’s the usage of his proposed library:

  1. function WRClass(name){
  2.     this.name = name;
  3. };
  4. WRClass.prototype.toString = function(){
  5.     return this.name;
  6. };
  7. WRClass.prototype.toLocaleString = function(){
  8.     return "locale: " + this.toString();
  9. };
  10. function WRSub(name, age){
  11.     this.parent(name);
  12.     this.age = age;
  13. };
  14. wr.extend(WRSub, WRClass, {
  15.     toString:function(){
  16.         return this.parent() + ": " + this.age;
  17.     }
  18. });
  19. var me = new WRSub("Andrea", 30);
  20. alert(me);                   // Andrea: 30
  21. alert(me.toLocaleString());  // locale: Andrea: 30
  22.  
  23. // last, but not least
  24. WRSub.prototype.explicitParentToString = function(){
  25.     return this.parent.prototype.toString.call(this);
  26.     // instead of
  27.     return this.constructor.superclass.toString.call(this);
  28. };
  29. alert(me.explicitParentToString()); // Andrea

And, see the blog post linked above for the source code to his implementation.

Posted by Ben Galbraith at 8:00 am
11 Comments

++---
2.9 rating from 41 votes

11 Comments »

Comments feed TrackBack URI

First of All, thanks for this entry in Ajaxian. Seconldy, please update the post since there is no jsClass (Ariel Flesler Project) but jClass from John Resig post. Thanks again.

Comment by WebReflection — February 20, 2009

@WebReflection

I’m sorry but why such vicious coding style?

Why would you name local variables as global constructors – Function, Object, etc. Then there are self executing functions without parenthesis around them.

It took me half an hour just to figure out what’s going on in this seemingly simple example. You’re obviously free to write in whichever way you wish, but what I see now makes it really hard to wade through, understand and comment.

I want to mention that Prototype.js, for example, does not overwrite all of the methods of subclass but only those that have `$super` as their first formal parameter. From what I can see, your implementation overwrites all of the methods, as long as same named method is present in a parent “class”. Speaking of methods, `typeof` function, as you probably know, will produce false positives in Safari (when applied to regex) – turning propeties with regex values into functions that try to call those regexes : )

I was also able to make your `extend` implementation work almost twice faster in FF [1], although IE seemed to be a bit slower (and I have no time to figure out why)

[1] http://gist.github.com/67500

Comment by kangax — February 20, 2009

Hi Kangax.
I will write a post my vicious coding style because reasons are more than one but please do not tell me that if you expect a Function, and an Object, as arguments, you cannot understand the meaning of a variable called Function, and another one called Object …

Parenthesis around self executed functions are not necessary when the function returns whatever value and as far as I know you are byte maniac as I am, aren’t you?

Overrides against obtrusive arguments … uhm, if I do not want to override I use the YUI strategy because for sure I cannot change every function definition, mine or not mine, because a library unshift arguments to send the original one. That is why I defined Prototype implementation the most obtrusive ever.

typeof function could be removed without problems and thanks for the tip while for your super fast implementation, as long as you use “my 4 step parent call” I strongly doubt your one will be faster (what I mean is that if you call a variable fn instead of Function do not expect better performances, whatever surrounds that function assignment).

When your version (and my updated one) will be finished, we can compare speed and code size (gzipped/packed as well) ok?

I am looking forward for the next challenge (and for comments like your as well)

Best Regards :-)

Comment by WebReflection — February 20, 2009

@WebReflection
> I will write a post my vicious coding style because reasons are more than one but please do not tell me that if you expect a Function, and an Object, as arguments, you cannot understand the meaning of a variable called Function, and another one called Object …

When I see an identifier named `Function`, I think of a global native `Function` constructor (not the local variable). Every time I see `Object` or `Function` in your code, I need to scan to the beginning of the function, to remind myself whether this is a local variable or it is actually a global constructor. But just to make things even “easier” for me, you also like to put “beginning” of the function in the end of the entire code (when passing it, say, to the self-executing function – the one that’s without parenthesis). I end up scanning your snippet back and forth, trying to figure out things that should be obvious, instead of focusing on the actual idea/implementation : )

Parenthesis are not necessary, but they make it easier to understand that a function you’re looking at (which can be quite lengthy) is not being just declared, but is also executed. I think Micheaux wrote about this issue some time ago (so I’m not alone with this argument)

If you want someone to comment on your code, don’t you want to make it easier for that “someone” to be able to first read/understand what that code does?

> Overrides against obtrusive arguments … uhm, if I do not want to override I use the YUI strategy because for sure I cannot change every function definition, mine or not mine, because a library unshift arguments to send the original one. That is why I defined Prototype implementation the most obtrusive ever.

Prototype.js also relies on function decompilation and that’s not a good thing.

> When your version (and my updated one) will be finished, we can compare speed and code size (gzipped/packed as well) ok?

I don’t really have intentions to “work” on my version or compete with yours. The reason I modified your implementation was to make it readable and remove unnecessary object creation during every constructor invocation (when testing for DontEnum bug). Did it not bother you?

Comment by kangax — February 20, 2009

> When I see an identifier named `Function`, I think of a global native `Function` constructor (not the local variable)
I do not usually pass global constructor, since if I do not need them, I wonder why I should leave that “word in the dictionary” alone when I can use it in a meaningfully way (but this is part of my post that I will write soon!)

> Parenthesis are not necessary, but …
JavaScript allows us to do stuff like {a:1,b:2}[arguments[0]]
I wonder if a superficial lecture of the code could be that helpful to make code specifications useless … I generally agree with you and Micheaux but “for 10 lines code” I do not think this is that big deal.

Whatever does Prototype library does not matter for this basic analysis about inheritance … I got on purpose a single weird case and I proposed/analyzed it for some common library. Every library has pros and cons, this is just a single stone in the ocean of possibilities each library offers for daily usage/development, unused toLocaleCase or not.

> The reason I modified your implementation was to make it readable

Honestly your version is probably more reliable (I cannot update my repository right now since the main hoster is maintaining) but is longer and in my opinion more complicated to understand (less linear)

At the same time, an array of method names (strings) than cannot contain a dot in the middle, is worthless in my opinion, since for compressors and simplicity I still think that “a.b.c”.split(“.”) is both more simple to maintain and bandwidth saver.

After all our concerns, there is still the style and thanks Lord we are not machines and we like to code in our way as long as we can understand our code (it took half an hour but you understood everything, same is for me if I read your code style compared to mine)

Finally, how much could we complain for a multiple scope library since we should read the full code instead of a single line?
{toString:function(){return”Regards :D”}}

Comment by WebReflection — February 20, 2009


I do not usually pass global constructor, since if I do not need them, I wonder why I should leave that “word in the dictionary” alone when I can use it in a meaningfully way (but this is part of my post that I will write soon!)

No, you’re not passing them into functions, but you use them in functions and when you do so, a reader needs to see whether the identifier resolves to some locally-defined variable, or if it is indeed a global constructor. The fact that “Object” is a global constructor is exactly why you shouldn’t use that “word in a dictionary” ;)


JavaScript allows us to do stuff like {a:1,b:2}[arguments[0]]
I wonder if a superficial lecture of the code could be that helpful to make code specifications useless … I generally agree with you and Micheaux but “for 10 lines code” I do not think this is that big deal.

Those were a bit more than 10 lines : ) Besides, being consistent is good. When you look at the code line-by-line, it is much easier to spot self-executing function if it is wrapped in parenthesis. Do those 2 characters really matter to you? Come on, don’t we have minification/caching these days?


Whatever does Prototype library does not matter for this basic analysis about inheritance … I got on purpose a single weird case and I proposed/analyzed it for some common library. Every library has pros and cons, this is just a single stone in the ocean of possibilities each library offers for daily usage/development, unused toLocaleCase or not.

I understand. Not many libraries take care of DontEnum bug fully, and taking care of most of the `Object.prototype.*` properties can be considered an edge case (aside from toString/valueOf). Prototype.js, also does “DontEnum” test in not most efficient matter – i.e. every time a “class” is created. (disclaimer: I’m one of Prototype.js developers)


Honestly your version is probably more reliable (I cannot update my repository right now since the main hoster is maintaining) but is longer and in my opinion more complicated to understand (less linear)

More reliable? Could you clarify?


At the same time, an array of method names (strings) than cannot contain a dot in the middle, is worthless in my opinion, since for compressors and simplicity I still think that “a.b.c”.split(”.”) is both more simple to maintain and bandwidth saver.

Agreed. dot-separated string looks pretty clean and is easy to maintain.

Comment by kangax — February 20, 2009

more reliable means I changed that typeof with an Object.prototype.toString.call(whatever) === “[object Function]” :-)
I’ll try to write down today more about my vicious code so you can understand better and make your considerations (if interested).
Have a nice WE

Comment by WebReflection — February 21, 2009

@Andrea/WebReflection ‘So I am sorry, but I am still asking you to demonstrate that for Huffman based compressors, dictionary length is not important’

I’m very disappointed, I took the time to write you an argumented reply showing how biased and irrevelant your demonstration is. You choosed not to publish it and instead write this comment pretending I didn’t provided any argument.

This is very childish… continue to truncate people post so you can look like you’re right on your own blog.

Comment by ywg — February 22, 2009

ywg … first of all it is sunday, I am not spending the entire day moderating comments. Secondly, your one was hidden by kangax one in my gmail account. As soon as I spotted it I published, you can check it.
I published this one as well, so you can stop to blame me for something I have never done in my blog. Regards

Comment by WebReflection — February 22, 2009

I’m very suspicious when posting on blogs… Sadly this kind of “incidents” have happened to me many times.

As it was not your intention, please accept my apologizes.

Comment by ywg — February 22, 2009

I found 2 bugs:

var wr_Root = wr.extend(
function() { alert("wr_Root"); }, {
Method0: function() { alert("Method0"); },
Method1: function() { alert("Method1"); },
Method2: function() { alert("Method2"); },
Method3: function() { alert("Method3"); }
});
var wr_Class1 = wr.extend(
function() {
},
wr_Root, {
Method0: function() {
this.Method1();
this.parent(); // alerts "wr_Root" ("Method0" expected, simple to fix)
this.Method2();
},
Method1: function() {
this.parent();
},
Method2: function() {
try {
this.Method3();
}
catch (e) {};
this.parent(); // alerts "Method3" ("Method2" expected)
},
Method3: function() {
throw new Error("hard to fix =(");
}
});
var Obj1 = new wr_Class1();
Obj1.Method0();

I’m just trying to find bugs in my own code, but they emerge only after I study and test similar code writen by someone else =)

Comment by Covex — February 25, 2009

Leave a comment

You must be logged in to post a comment.