My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Saturday, February 28, 2009

On JavaScript Inheritance Performance - One Step Back

Few days ago I wrote a post about this argument, proposing an alternative "best option" way to use an injected parent in each method.

As soon as more developers read my post, more sparkles came out from the fire: true classical inheritance simulation via missed methods in the chain, exception during methods execution that could trap the temporary injected parent, and other interesting stuff again.

At the end of all these tests, benchmark, and libraries evaluation, I decided to step backward about my proposal, making things simple, logic, and extremely fast (as much as possible).

My Conclusions


  1. if we inject a parent, we have to change and/or wrap the original method, adding noise in the execution and inevitably more operations to perform (read: less performances)

  2. for each inherited metod, even if it is the same of the parent one, we need to add functions and code which means more RAM and less portability

  3. more we over-mess the simple JavaScript inheritance stack, less control we'll have for debugging and maintenance

  4. using strategies like the one adopted from the YUI library could only add confusion because if we put the instance in a parent method, the super one or the super.super.super (and go on), its parent will be still the original one and if we call this.superclass in the wrong context, results will be always unpredictable for that method (unless we did not write down every specific case, if the instanceof A called that method ... if the instanceof B called that method, etc etc)


Accordingly, the fact we are all lazy developers should not mean we can loose performances, use more RAM, make things more complicated than we need, specially in this Web era where devices with limitations could use our code without problems (iPhone) but only if performances are reasonable and downloaded code size is, again, reasonable.


The new best option to use a parent method

Following the test proposed in the old post, this is how I can obtain best performances even against classical way to use parent methods:

function WRSub(name, age){
// classical and debug prone parent call (fastest)
WRClass.call(this, name);
this.age = age;
};
wr.extend(WRSub, WRClass, {
// WebReflection parent method call in closure
// faster than runtime method resolution:
// WRClass.prototype.toString.call(this)
toString:(function(toString){
return function(){
return toString.call(this) + ": " + this.age;
}
})(WRClass.prototype.toString)
});



Summary

I love performances, readable code (big lol from kangax? :P), code portability, logic execution, and all we need is already there in the language.
We do not need to over mess the inheritance chain, the only thing we need is a god way to extend able to understand if the browser is missing hidden methods (toString, etc) and nothing else.
Once we have this good way to extend a constructor, everything else will be fast, readable, debug prone, and compatible with every possible situation, without breaking our neck to understand why that method in that case behaved differently, or that parent did not change as expected, etc etc ... robust, reliable, and fast code? Forget all these parent/$super/superclass implementations, put what you need in a closure to simplify and speed up the code and that's it, as my new benchmark shows where my clean and simple implementation bites every other library, even the classical full namespace way.


News about WebReflection Library

I have added a couple of things which are truly common on daily basis development. You can find a list of all JS 1.6 Array methods to use with whatever list you want, DOM Collections included.

wr.Array.forEach.call(document.getElementsByTagName("div"), function(div, i, collection){
div.innerHTML = "I am div " + (i + 1);
});

Those are compatible with ECMAScript specs, native performances for updated browsers, best performances for IE and others.
The wr.Object.forIn is the classical for in loop plus hidden methods in IE, something we can use to extend constructors or to inspect objects.
I am considering to put the hasOwnProperty check by default to loop only properties assigned, and not those inherited, but for extending purpose, we need to loop everything.
setInterval and setTimeout are natives or wrapped to support extra arguments as well.
wr.id is the classic method to generate an expando or a unique id for each call while wr.is is a function with all native checks via Object.prototype.toString.

The purpose of the library is not (yet) to substitute other libraries, it is just about basic things we always need, focused on performances, compatibility, etc.
I'll add more, but so far enjoy the extend, now free of bugs since different, the forIn, the is, the id, and current Array implementations.

9 comments:

reiss said...

Hi Andrea,

This is basically the same as I proposed in the comments on your other post:

http://webreflection.blogspot.com/2009/02/on-javascript-inheritance-performance.html

I'm a bit disappointed you haven't included j3Class or JooS in your tests. I've created the tests for wr Class over at:

http://www.projectcss.net/inheritance/

To be honest it was a bit of a chore. If I have read your post correctly you are suggesting that every method that requires a parent call should be wrapped in a closure? This leads to extremely verbose code having to type the method name 4 times for each function. Parent constructor calling threw me a bit at first as your implementation uses an anonymous function and so the way in which it is called isn't consistent with the rest of the parent calling methods (slightly).

Having ran the tests on the link above wr Class method calling is no faster than j3Class so I wonder why you have opted to type Parent.prototype.methodName everytime? Here is a quick comparison:

http://www.projectcss.net/inheritance/syntax.html

This is by no means a dig at your implementation, so please don't take it that way. I'm just curious why you have chosen to do it this way?

Thanks,

Reiss :)

Andrea Giammarchi said...

Reiss, wait a moment ... I did not suggest to wrap everything, only and only if necessary those methods whose would like to call a parent one, as is in my example (toString).
Accordingly, I would have written your Syntax example like this:
var Fighter = wr.extend(
function(sName, sHairColour, sEyeColour,iHeight, iWeight){
Human.apply(this, arguments);
},
Human,
{
className: 'Fighter',
chain: (function(chain) {
return function(){
return chain.call(this) + ' Chain1 Fighter(' + this.className + ') >';
}
})(Human.prototype.chain)
}
);
which I suppose has best perfromances.

reiss said...

Hi Andrea,

"I did not suggest to wrap everything"

I never said you suggested to wrap everything. I said:

"you are suggesting that every method that requires a parent call should be wrapped in a closure?"

I think if one is going to use this type of pseudo class syntax then it's important to be consistent with it. So if you say

"// WebReflection parent method call in closure"

Then that pattern should be used for anything that calls its parent, hence why I did this in the syntax link above. Also using:

"Human.apply(this, arguments)"

Seems to be a lot slower than using ".call" with explicit arguments. I tested this while doing j3Class and there was a definite difference in speed.

Also by hard coding the Parent class name in the child class spec, you could end up with a lot of changes if you decided to rename the parent class e.g. from Human to HomoSapien.

Reiss :)

Covex said...

@Andrea, I think you will have problems while refactoring your code, because you include name of parent class in declaration of new class's method.

But your idea is great ! And you reached limits of performance... Almost =)
> which I suppose has best perfromances.
Not any more =) I used your idea and made a new variant. Please, have a look:
http://beatle.joos.nnov.ru/inherit2/. Мy solution if faster by 1-10% in all browsers =)

But I must say, that I won't use it in real life, because of it's syntax. I just do not like it, I'm too lazy and it is not compatible with my old code =(

Any way, thank you for competition, I think it is over now, and you will be a winner only if you will organize your classes well.

reiss said...

@Covex - Hi, your test uses the old version on j3Class and doesn't give accurate results. I've uploaded the same test but with the newer version:

http://www.projectcss.net/inheritance/joos.html

Thanks,

Reiss :)

Andrea Giammarchi said...

@Reiss, I read more carefully your idea and I think it is generally convenient.
The only problem I can spot is when you need to re-use an extended prototype with another class. Since you trap the parent, debug with a method which contains multiple parents could be hard and for every extended method you need to rewrap the prototype. I trap a method but it is an extreme performances case and nothing else (and yes, in your test my solution wins and respects hidden methods in IE as well).
What I tried to say in this "Step Back" post is that explicit calls are much better than whatever injected or static shortcut solution we adopt.
Try to consider that prototypes could be added/changed runtime as well, I know cases are few but if you trap the parent you cannot add another method later able to use that parent. As summary, explicit calls, if we need extreme performances for ONE METHOD (not every one) then we could wrap the prototype method with a closure that contains one or more parent methods. The good thing is that my suggestion could make function portables and we can add even methods which have not been inherited.

@Covex, your solution seems to be fast as mine is, please do not rely in 1 to 10% perfromances with JavaScript because there is no benchmark so accurate. call against apply, well, it is all about convenience but when arguments are exactly the same, I usually prefer apply :-)

reiss said...

Hi Andrea,

I'm not quite sure what you mean by:

"debug with a method which contains multiple parents could be hard and for every extended method you need to rewrap the prototype"

Do you have an example of the above?

"(and yes, in your test my solution wins and respects hidden methods in IE as well)"

To be honest there is hardly anything in it in terms of speed, and what is lost, is more than made up for in code maintainability, readability and bytes (which I thought you'd like being a "byte maniac" lol).

"Try to consider that prototypes could be added/changed runtime as well, I know cases are few but if you trap the parent you cannot add another method later able to use that parent"

The point of a class is to define an object, so I'm not sure when you would want to add to that definition after it has been defined? And if you did want to add to it what would be wrong with doing this?

Constructor.prototype.newMethod = (function(base){
return function(){
//some stuff
//parent call
base.newMethod.call(this)
}
})(Parent.prototype);

"The good thing is that my suggestion could make function portables"

In what way are they more portable, the parent name is hard coded, which I thought would make them less portable?

Thanks,

Reiss :)

reiss said...

Hi Andrea,

Had another thought...

Why not get rid of the overhead involved in copying over properties to the prototype and checking for the enumeration bug?

I've added a j3Class2 which does this, and a new test "Create Instantiate Call". If you check the following links you'll see there is a big difference between the two:

http://tinyurl.com/bloe2q
http://tinyurl.com/bzlax6

Thanks,

Reiss :)

kentaromiura said...

OT:
Andr3a, did you use the (new Date).getTime() while you're benchmarking on IE?

Because is inaccurate:
http://ejohn.org/blog/accuracy-of-javascript-time/
you should make your benchmarking (only in IE) in something like this:
http://www.codeplex.com/IEJst

sorry fot the OT, but I think is important ;)