Thursday, April 30, 2009

vice-versa sub project: css2xpath

Update

I completely re-wrote the test suite. You can check a live example here where results are compared with Sizzle selector engine against the common W3C page.



After this Ajaxian post and some opinion exchange in its comments, I decided to extract the css2xpath function I used inside experiments in vice-versa project, creating a separated project specific for this purpose that some how could be interesting or useful.
Bugs, problems, questions, and helps are more than welcome, so far I focused my attention to SlickSpeed test selectors and nothing else but at least for that it seems to be stable and reliable.

[OT] Swine Flu ... Service Unavailable!

A quick one about how things are not that different whatever country you live ... in a page created to inform and to help to prevent this new killer flu, the NHS symptoms checker link brings to a Service Unavailable page.
I wonder if countries organizations will never be ready for whatever emergency we have.
Hoping people will be able to obtain more info directly from directgov website, we can do 3 things so far: CATCH IT (using tissues to sneeze or cough in), BIN IT (we don't want to travel that far with our dity tissue, do we?), KILL IT (this one is quite difficult to explain and I guess they did not choose "the proper verb" for the last action ... seriously!)

Wednesday, April 29, 2009

Drip under control via another IE memory leak tentative ...

Apparently, using this strategy I can obtain a flat line in Drip monitor:

// function used to remove a node, every attached
// attribute and every nested node via the same procedure
var destroy = (function(destroy){
// WebReflection IE leaks attemp!
function $destroy(node){
while(node.lastChild)
destroy(node.lastChild);
if(node.parentNode)
node.parentNode.removeChild(node);
};
return destroy = destroy.clearAttributes ?
function(node){
if(node.clearAttributes) // Hedger suggestion
node.clearAttributes();
$destroy(node);
} :
$destroy
;
})(document.createElement("script"));


// used to remove everything
function destroyAll(){
destroy(document.documentElement);
};

// used to avoid leaks when the page is refreshed
// or the url is changed
if(this.attachEvent)
attachEvent("onunload", destroyAll);


reasonable performances and apparently a reliable solution.
tested via this code:

attachEvent("onload", function(){
detachEvent("onload", arguments.callee);
for(var i = 0; i < 1000; i++){
a.push(document.body.appendChild(document.createElement("div")));
var node = a[i];
node.innerText = i;
node.obj = a[i];
node.attachEvent("onmouseover", function(){
node.obj.other1 = node;
});
node.onclick = function(){
this.obj.other2 = node;
};
};
});

The procedure is based on assumptions I did in this post about div[expando] and div.removeAttribute(expando)

Tuesday, April 28, 2009

The Fastest Selector Engine For FireFox 3.0 ?

I am putting some effort to make vice-versa project a good, production ready, alternative against common libraries, and since these days I am working hard with XML, XSL(T), and XPath I decided to try an experiment implementing CSS to XPath translator for FireFox 3.0



With version 3.1 we will have querySelector and querySelectorAll but version 3.0 is still the most used one, FireFox speaking!

The nightly build of vice-versa introduces a new file, mainly used for personal experiments via vice-versa library and the first experiment is an improved document.query for those browsers with document.evaluate support without document.querySelectorAll.
Unfortunately Internet Explorer < 8 does not support XPath queries over (x)HTML but at least for FireFox 3.0 I have obtained best overall performances via SlickSpeed Selector Speed Test comparing latest version of each library such Dojo, DOMAssistant, Sizzle (congrats for the new site!), Sly, and finally vice-versa project.
Here the summary, from faster to slower:

vice-versa 760ms
Dojo 1.3.1 868ms (2 tests failed)
DOMAssistant 880ms (good stuff Robert!)
Sly 890ms
Sizzle 914ms

Now, the good part is that nowadays every selector engine seems to perform truly fast and in whatever browser you like, but the "even better" part is that the experiments file in vice-versa is 1Kb (minified and gzipped) and with just "that Kb" it is possible to use a specific and fast selector engine for FireFox 3.0.

Finally, the experimental CSS to XPath translator cannot support pseudo searches like :hover, :active, or similar, due to the XPath nature, a query language mainly designed for XML where CSS, unless we do not transform it via XSLT, has a complete different meaning from the one used in (x)HTML.

I wonder now who will be able to create better overall performances for FireFox 3.0 ( changing my translator implementation, for example ;) )

Saturday, April 25, 2009

JavaScript Is A Toy? So C#, Java, PHP, and Python Are!

I wrote a truly big post about this subject ... but it was nervous and not that technical as well. I decided then to simply post this image, hosted in Pam webOS Project page:

Think about the right box, it should be the state of the current Web 2.0 Era ... now think who is in charge to maintain the layer between your server application, and the customer ... I am quite sure 90% of cases will be a Web Designer, rather than a Senior skilled JavaScript Developer ... now wonder why if you ask for JavaScript skills plus Photoshop, you will rarely being able to make right profits from your business. JavaScript is not a toy, and "web speaking", is one of the most important programming language your team should know, it's time to stop to underestimate it.

Friday, April 24, 2009

div[expando] = null OR div.removeAttribute(expando)?

Today we had an interesting conversation in the jQuery developer mailing list, and "surprise", it was about Internet Explorer and memory leaks.
I instantly started some test to understand what the hell is going on there ... and I encountered a dilemma which is not simple at all!

div.prop = 1 IS NOT div.setAttribute("prop", 1)

Some developer thinks that the usage of setProperty and getProperty is basically the same of a manual assignment as generic DOM Element property ... well, they are wrong.
First of all, if we are working with XML we all know that it is not possible to assign a property in this way:

xmlNode.prop = 123; // Error!!!
xmlNode.setAttribute("prop", 123); // OK, but be careful!

The main difference between manual property assignment and the usage of setAttribute/getAttribute is that we are changing HTML or XML properties of the node without control.
The reason is simple, how manual properties are assigned/retrieved is browser dependent, and the only way to be sure we are not modifying a node only internally is to use the setProperty method.
Obviously, it is not that simple in any case still because of browsers implementations. Here a couple of examples:

var div = document.createElement("div");
div.__expando__ = 123;

// FireFox and other browsers
div.getAttribute("__expando__"); // null

// Internet Explorer
div.getAttribute("__expando__"); // 123

Accordingly, the only browser that manages direct assignment as setAttribute shortcut is Internet Explorer.

Moreover, cases are really differents between IE and other browsers ...

While everybody uses setAttribute as a method to change the node in its HTML or XML nature, Internet Explorer uses this method to assign whatever value. Example:

var div = document.createElement("div");

// set an object as an atribute
div.setAttribute("__expando__", {a:"b"});

// FireFox and others will obviously convert the second argument as a string
// to make node representation possible as HTML or XML

// Internet Explorer will set that object as an hidden attribute
// which means that that property will not exists in the
// HTML or XML representation of that object, but will be a property

// this call ...
div.getAttribute("__expando__");

// will generate [object Object] in FireFox and others
// not because we are dealing with an object
// but because the object has been converted into a string
// during setAttribute assignment

// In internet Explorer that cal will retrieve the original
// Object {a:"b"} and this call will produce: "b"
div.getAttribute("__expando__").a; // b in IE, undefined in other browsers


Another interesting thing is that we cannot use delete div.__expando__ in internet explorer for the reason I said before: we cannot delete an attribute via delete, we need to use the removeAttribute method.


var div = document.createElement("div");
div.__expando__ = 123;

delete div.__expando__; // Error in IE

But since IE set attributes of any type, we should bear in mind that everything that is not instanceof Object will be present in the HTML representation of that Element.

var div = document.createElement("div");
div.n = null;
div.obj = {a:"b"};

alert(div.outerHTML);
// <DIV n="null"></DIV>

Accordingly, whatever reason we have behind leaks, we should bear in mind that if that node has been stored somewhere, it will bring with itself every property = notAnObject; included numbers, null, whatever.

To create a memory leak we can simply do something like this:

var div = document.createEement("div");
div.__expando__ = div;

That's it, even if we remove that div and we delete its related variable (div), IE will not remove that node (if present in the DOM) from its used RAM.
Since some developer know it, they obviously try to avoid leaks like this using a syntax which makes sense but which leave traces of "their operate":

var div = document.createEement("div");
div.__expando__ = {iama:"complex object", orwhatever:"value"};

// when div is removed/destroyed
div.__expando__ = null;

// well, as we have seen before:
div.outerHTML;
// <DIV __expando__="null"></DIV>


To completely remove properties then, all we need to do is to use removeAttribute:

var div = document.createEement("div");
div.__expando__ = {iama:"complex object", orwhatever:"value"};

// when div is removed/destroyed
div.removeAttribute("__expando__");

// well, as we have seen before:
div.outerHTML;
// <DIV></DIV>


If you test with a memory leaks detector, you will realize that the result is exactly the same while the final node, leaked or not, will be as clear as possible from other libraries and conflicts will be reduced as well (getAttribute("__expando__") is setted as null will return the string "null" rather than the null value usually returned by getAttribte when it is not there).

What's next? Understand how much performances will be affected and when it is worthy to use removeAttribute rather than = null but at least now we have proven that setAttribute, getAttribute, and direct property assignment are not the same thing.

Thursday, April 23, 2009

vice-versa project, a philosophy rather than a library


Array.forEach(document.all, function(node, i, all){
// vice-versa project
});

well, there are few things I can say here, and first one is: please, if interested, help me!
The second one is my vision, a web where rather than criticisms, there is more collaboration to obtain good overall performances improving code quality.
This is not my old JSL project, this is a new groove, a new project, a non library, something I hope you will all appreciate.
But, if not, I am here to discuss about it :)
vice-versa project
Have fun with Web Development!


Update
Implemented a TaskSpeed test file for vice-versa project. Here first results ordered by average.



browser Pure vice Moo qoox dojo YUI AVG
Dom versa Tools doo
--------------------------------------------------------------------
Safari 4 198 114 392 205 246 410 260.83
Chrome 1 251 273 589 279 336 692 403.33
Opera 10 215 206 764 267 311 659 403.67
FireFox 3 404 670 2962 3557 1197 792 1597.00
IE 8 876 1031 10312 1470 2267 1749 2950.83
IE 7 795 1767 8174 1734 7796 3329 3932.50
IE 6 3941 4686 67590 16611 22388 24591 23301.17

Wednesday, April 22, 2009

Internet Explorer with V8 Engine :: A Partial Reality

Update now it works with Internet Explorer 7 and 8 with O3D plug-in enabled.

Today, via ajaxian, I discovered the new Google O3D project and as a nerd first, and a developer after, I instantly read the API.
Few seconds and I spot this: Use the V8 engine

OK, OK, whatever code you read there will not instantly work ... you have to understand the library and how to retrieve the dreamed eval function to make JavaScript execution possible inside the extremely fast V8 engine.

Obviously V8 integration is great with FireFox and other browsers, but a bit different in Internet Explorer.

This limitation is about the DOM manipulation, something that would make IE a good browser, but everything else, executed via V8 engine and without browser engine references, will perform 5 to 100 time faster.

Function creations, optimized loops, numbers, string manupulation, etc etc ... if we could use a little plugin to fully integrate V8 inside Explorer, we could all forget nowadays benchmark because it will perform better than many other browsers!

Bearing in mind the relationship between V8 variables, different 100% from JScript vars, and latter variables, we can delegate computational tasks to this engine makin IE fast as it has never (and will probably never) been!

What I have so far, is just a poor example about V8 inside IE possibilities, a page that does not work if you do not have V8 inside Exlorer and you do not enable its usage everywhere.

Write a loop like this in the textarea:

for(var a = [], i = 0; i < 500000; ++i)
a[i] = Math.random();

and switching between default and V8 you can read that the difference is about 5 times slower with the default Internet Explorer JavaScript engine, and Google V8.

Still, so far what I can spot as pros and cons is this:
Pros

  • mathematical operations up to 100 times faster

  • computational functions speed up

  • common AI patterns (AStar) finally usable in the web

  • games, chess, cards, whatever involves logic, speed up



Cons

  • even if you assign a V8 Array.prototype.forEach to native Array.prototype.forEach, the plugin cannot solve engine related dependencies problems ... so it will be simply slower, much slower, because of the data interchange between the method executed externally, and the original engine

  • DOM is not integrated, V8 in IE does not understand nodes so no way to speed up common libraries or add prototypes to native HTMLElement constructor

  • last, but not least, with my laptop I cannot even test the example page I posted before because the JS is not that perfect (Google guys, makeClients accept an id but you check .id === "o3d" in the loop ... does it make any sense?) and my card apparently does not support OpenGL or whatever library the o3d plugin requires



As Summary


This was just a quick post and an experiment which in my opinion is already god enough. I have a vision, about Internet Explorer 8.1 ... those guys created an April fool that is everything but not a fool. Come on Microsoft, let others replace your old, problematic, slow, JScript engine, and let us developers be free to create Web Applications as we would like to do ... please give to Google whatever they need to make V8 IE integartion possible, and let us dream about a not that far possible Web future!

Sunday, April 19, 2009

Last Minute - Blogger and Spam Blogs

Nice one, 10 minutes after I published my last sarcastic comment about IE sort implementation, blogger sent me an email communicating that my blog has been marked as a spam blog. I am sure if I was an X-Files actor I would have though instantly about conspiracies or some sensible M$ engineer move ... but as web developer and experienced programmer I am sure it is a coincidence and any software is perfect: it does not matter who is behind that software!

P.S. ok, an entry for this fact is probably too much but if you find something weird please be patience, I should be back soon ;)

Internet Explorer and its inefficient sort implementation

This is just a quick post about how much IE team cares about JavaScript performances ...

// generate an array of 1000 elements
var a = new Array(1000).join(",.").split(","),
calls = 0;

// call sort method incrementing the conter
a.sort(function(a, b){
++calls;
// return zero, nothing should change
return 0;
});

// discover the monster
alert(calls);

Guess how many times Internet Explorer compare results? 17583 times, against a.length - 1 in other browsers.

Nice stuff guys, I guess for big arrays a manual reimplementation could perform better, am I wrong?

[COW] A Generic ArrayObject Converter

Few days ago I wrote about a fast Array slice implementation for every browser, a callback able to convert "every collection" into an Array.
For collection, I mean every instance with a length attribute and an index access Array like. This object, for example, could be considered as a collection:

var generic = {
length: 2,
"0": "abc",
"1": "def"
};

Above instance is a basic model of most common libraries such jQuery, prototype, base, and every other based over an Array like prototype.

Some library could coexist in the same page without problems but not every library implement a method to create a copy of another collection into a new instance of the library itself.
As example, a jQuery or Sizzle result into another instance, a generic DOM collection into a jQuery object, etc etc.
All these instances could be passed via Array.prototype.push to be populated, and thanks to this peculiarity we can obtain every kind of instance from a collection via Array conversion.

var toArrayObject = (function(push, e){
// WebReflection - Mit Style License
e.base = {length:0};
e.slice = (function(slice){
// portable slice
try {
slice.call(document.childNodes);
var $slice = slice;
} catch(e) {
var $slice = function(begin, end){
if(this instanceof Object)
return slice.call(this, begin || 0, end || this.length);
for(var i = begin || 0, length = end || this.length, len = 0, result = []; i < length; ++i)
result[len++] = this[i];
return result;
};
};
return $slice;
})(Array.prototype.slice);

// prototype ready callback
return function(constructor){

// assign the "class" prototype or the base object
// as anonymous prototype
e.prototype = constructor ? constructor.prototype : e.base;

// create a new instance of anonymous
var r = constructor === Array ? [] : new e;

// inject via push the collection, passed as function scope
push.apply(r, e.slice.call(this));

// return the new ArrayObject instance
return r;
};
})(
Array.prototype.push,
function(){}
);

With above code we could quickly transform, for example, a DOM collection into a jQuery instance without using the jQuery engine:


var result = toArrayObject.call(
document.getElementsByTagName("div"),
jQuery
);

result.remove();


Another example could be a transformation between jQuery and prototype:

var prototyped = toArrayObject.call(jQuery(".test"), $);


The same could be obviously done via Array, even if this is a non-common case:

toArrayObject.call([1, 2, 3], jQuery);
toArrayObject.call(jQuery(".test"), Array);


The usage of call makes prototype assignment that simple:

jQuery.fn.convertTo = toArrayObject;

// as prototype or other library conversion
jQuery(".test").convertTo($);


That's it, if you never wondered about elements injections, libraries conversions, or Array like object management, this code could be a truly quick and portable solution.

Tuesday, April 14, 2009

Essential Selector - Cross Browser LightWeight Selector Engine

Thanks to new DOM methods introduced recently in most common browser, e.g. querySelectorAll, we will not hopefully need full libraries to implement common CSS selectors. Nowadays, this could be the basement to create any kind of selector engine but we are still stack with old fashion browsers a la Internet Explorer version 6 or 7, both far away from W3 standards and with the slowest JavaScript engine in browsers panorama.

At the same time, whatever great selector API/engine we have under control, most used selectors are really few:

  • #id

  • .class

  • tag

  • tag.class



Reasons behind this fact are different, but in my opinion the most valid one is that Web Developers use CSS selectors in the same way they create CSS files and since CSS has become standard only recently thaks to Internet Explorer full CSS 2.1 support (n.d. other browsers are working on the CSS 3 since ages ...) our CSS files and our selectors will be that simple for long time.

Accordingly, and since we have some intermediate and cool prototype such getElementsByClassName, all we could need is just a basic selector engine able to retrieve nodes in the fastest possible way.

Of course, if querySelectorAll is present, this method will be a must, but what if it is not available?

Sizzle library is one of the most famous selectors engine so far, but we need "to move" 4KB of minified and gzipped code (not that much but often more than necessary) to obtain something simple, specially if precedent selectors are the only one we use in our project.

The Essential Selector Library

Maybe it sounds obvious, but to cover first 3 selectors in the list all we need is the fast getElementById, the standard getElementsByTagName, and the un-standard getElementsByClassName, easy to implement for old browsers. querySelectorAll? Superfluous in this case, but obviously still welcome! Above 4 selectors are the only one considered for performances in my last tiny library: about 1Kb minified and gzipped, suited for libraries and/or GUI development.
You can have a look directly in my repository to understand what will perform truly fast in every browser and what will perform in a reasonable time.

Essential Selector Philosophy

#id, .class, tag, and tag.class selectors will be fast for every browser while more complex selectors will be browser dependent. The main focus is into most used selectors but if you decide to use a specific one:

// CSS selector example
$e("div ul.myclass p");

recent browsers will perform in about 1 milliseconds while old browsers will perform a clean runtime CSS specific modification. This means that these browsers will have approximately the same delay for a selector like "div p" and "div p #content ul li.testcase" but at least, if the selector is compatible with the browser CSS engine, the result will be the same for every browser.
Moreover, due to the light size of the library, those bugged browsers will not be perfectly supported. As example, thre is a version of Opera which does not understand className in upper case ... well, this is not our problem, it is a browser specific bug so the browser vendor should solve it. The same is for other weird cases ... come on, we cannot consider every alpha/beta/unstable/intermediate/old version, so if the CSS works, the browser will respond as expected.
This is the philosophy behind this simple selector engine, where a search like

$e("div p")

will make sense, while another one like

$e("div[class^=whatever]")

will not, because of the not yet that standard chose selector.

Summary

Do you want a small footprint selector which works OK with daily basis environments? Try out Essential Library, otherwise I just gave you a valid, full compatible, alternative ;)

Wednesday, April 8, 2009

WebReflection PureDom baseline for TaskSpeed

I just had a quick and nice chat with Peter E Higgins and I am proud to announce that my PureDom test has been integrated into TaskSpeed test suite.

Why PureDom

We all know how many benefits libraries give us on daily basis about Web 2.0 supersonic development, but as both libraries author and users, we should pay attention.
Web Developers: sometime we delegate to our cool library some simple task which could be easily implemented manually. If performances are a problem we should use libraries only when and if necessary. There are a lot of best practices for each library and all we need to do is to read these practices to be sure our way to code is of course elegant, but not silly.

// this is silly
$("#items li.selected").each(function(){
$(this).css("color", "red");
$(this).css("background", "blue");
$(this).width($(this).parent().width());
});

// ... while this makes sense
$("#items li.selected").each(function(){

// cache the magic object
var $this = $(this);

// use full lib features, avoid redundant code
$this.css({color:"red", background:"blue"});

$this.width($this.parent().width());
});


Libraries Authors: our main purpose is to define a standard within the library logic and behavior to obtain best "results in zero efforts". Sometimes we could think about light layers between our super cool high level method and pure DOM in order to preserve performances for basic tasks. We all know the most problematic and slow browser, version whatever, is still Internet Explorer, and obviously more we overload the library for this browser, worse the latter one will perform. I hope the PureDom baseline will help all of us to compare results, specially for simple tasks where even the code size is not that different between the chosen lib and the manual DOM execution.


Tricky Monkey, but this is NOT "Python vs ASM"

I know some PureDom task could be interpreted as a "cheat" but honestly, what I have done, was simply follow instructions to create the result. That is my meaning of manual task implementation, and that is why libraries will have hard time against first column. It does not matter if libraries are slower than pure DOM, and it is quite obvious they will be slower, but more these libraries will be close to DOM results more advantages we will have using that library rather than the other one (more competition as well and we like it!)

Have fun with Web development and JavaScript! ;)

Tuesday, April 7, 2009

JavaScript Static Collection - A New Type Of Variable

Nowadays, I am working over a crazy manager between an XML with form information, another XML with custom rules to drive the XML with form information (DropDown onchange events, validation, and much more) and both to drive a runtime Ext.form.FormPanel with runtime components manipulated runtime via those two XML and over regular (x)HTML ... a runtime nightmare, but it is not the point.

While I was working over an Easy XPath Library for (x)HTML, trying to obtain best performances with the black sheep Internet Explorer, I ended up in an msdn page which explains the attributes HTML Element property.

Funny enough, that kind of property has truly a weird behavior, it is between VBScript and JavaScript. There are two ways to access its key/value pairs, the normal and logical one, plus the WTF!!! one. Example:

for(var
attrs = document.body.attributes,
len = attrs.length,
i = 0;
i < len;
++i
)
attrs(i) === attrs[i]; // true

The first though was: damn it, somebody poisoned my coffee again! (where the first time was when I read about Microsoft adopting WebKit as browser engine ...) but after few millisecond of brain delay, I realized that was brilliant!

The StaticCollection Type


Every Function in JavaScript has a length property, which tell us how many arguments that function accepts. In a completely dynamic language as JS is, this property has probably never been used, specially because every function has an ArrayObject arguments variable injected for each function call (but I guess would be interesting to use checks like: arguments.length > arguments.callee.length, for secret extra arguments to send ... anyway ... ).

Peculiarity of this property, we cannot overwrite it!
In few words, it is possible to create an Object via Function with immutable access over a collection, array, ArrayObject variable. This is the code:

var StaticCollection = (function(
name, // function name to assign
join // toString method from Array.prototype
){
// (C) WebReflection - Mit Style License
return function(){
var args = arguments, // trap sent arguments
length = args.length, // just a shortcut
i = 0, // used to assign values in order
callback; // Function to create and return
eval(
// create a function with a $name and $length arguments
// to block its length property
"callback = function ".concat(
name, "(",
new Array(length + 1).join(",$").substring(1),
// return first sent key from external arguments
"){return args[arguments[0]];}"
));
// assign in order properties via index ([0], [1], etc)
while(i < length)
callback[i] = args[i++];
// set the name for those browser where
// the function name is not present (mainly IE)
callback.name = name;
// set the useful toString method
callback.toString = join;
// return the FunctionObject
return callback;
};
})("StaticCollection", Array.prototype.join);

Due to fixed length behavior, the evaluation is absolutely necessary but not scary at all, since the string does not contain anything important, it is a simple wrap.
For those in Rhino or with some arguable rules about eval, here there is the intermediate step via Function:

...
i = 0,
callback= Function(
"args",
"return function ".concat(
name, "(", new Array(length + 1).join(",$").substring(1),
"){return args[arguments[0]];}"
)
).call(this, args);
...

which result is exactly the same but with a little bit of overhead caused by useless runtime Function execution.

StaticCollection Goals


  • one variable with static properties access plus indexed

  • indexed and called properties are comparable

  • indexes are mutable, perfectly suitable for relations between different objects/nodes/values

  • is the only quick and dirty way I know to make a collection immutable

  • something else, you'll find out :D



The StaticCollection Weirdness In Action


// Creation: same result
var sc1 = new StaticCollection("a", "b", "c"),
sc2 = StaticCollection("a", "b", "c"),
sc3 = StaticCollection.call(this, "a", "b", "c"),
sc4 = StaticCollection.apply(this, "abc".split(""))
;
/** [sc1, sc2, sc3, sc4].join("\n");
a,b,c
a,b,c
a,b,c
a,b,c
*/


// Properties Access
var sc = new StaticCollection("a", "b", "c")
sc(0); // "a"
sc[0]; // "a"
for(var key in sc){
key; // "a"
break;
};


// Immutable Properties: length + access via call
var sc = StaticCollection("a", "b", "c");
sc.length = 0;
sc.length; // 3
sc(1); // "b"
// sc(1) = 123; // error!


// Multiple access manifest
var sc = StaticCollection(456);
sc[0] === sc(0); // true
sc[0] = 123;
sc(0); // 456
sc[0]; // 123
for(var key in sc)
sc[key] !== sc(key); // true


// Type: function
typeof StaticCollection(1,2,3);


// Native toString: [object Function]
Object.prototype.toString.call(StaticCollection());


// Array Convertion:
var sc = StaticCollection(1,2,3);
sc.slice = Array.prototype.slice;
var a = sc.slice();
var b = Array.prototype.slice.call(
StaticCollection(4, 5, 6)
);


// Index Sort
var sc = StaticCollection(2, 1, 3);
sc; // 2, 1, 3
sc[0]; // 2
Array.prototype.sort.call(sc, function(a, b){
return a < b ? -1 : 1;
});
sc; // 1, 2, 3
sc[0]; // 1
sc(0); // 2


// Double indexOf
StaticCollectionSearch = function(value){
for(var
result = [-1, -1],
length = this.length,
i = 0;
i < length;
++i
){
if(this(i) === value){
result[0] = i;
break;
}
};
for(i in this){
if(/^\d+$/.test(i) && this[i] === value){
result[1] = i >> 0;
break;
}
};
return result;
};
var sc = StaticCollection(2, 1, 3);
Array.prototype.sort.call(sc, function(a, b){
return a < b ? -1 : 1;
});
StaticCollectionSearch.call(sc, 1); // 1, 0


// Array prototypes behaviour example
var sc = StaticCollection(1,2,3);
Array.prototype.shift.call(sc);
sc; // 2,3,
sc[0]; // 2
sc(0); // 1
sc.length; // 3


Crazy enough? The manually minified version is under 300 bytes, so let's start to play with ;)

/*WebReflection*/(function(n,j){this[n]=function(){var a=arguments,l=a.length,i=0,f;eval("f=function "+n+"("+new Array(l+1).join(",$").substring(1)+"){return a[arguments[0]]}");while(i<l)f[i]=a[i++];f.name=n;f.toString=j;return f}})("StaticCollection",Array.prototype.join);

Have fun!

Friday, April 3, 2009

A fast Array slice for every browser

It is an extremely common task and one of the most used prototype in libraries and applications: Array.prototype.slice
The peculiarity of this prototype is to create an Array from an Array like Object such arguments, HTMLCollection, other kind of lists as jQuery results.

Every browser, except Internet Explorer, allows direct calls via native prototype obtaining, obviously, best performances.

In IE, we have different ways to make this task possible, and these ways are mainly classic loops, or the isArray check to know when it is possible to perform the native call or not.


isArray = (function(toString){
return function(obj){
return toString.call(obj) === "[object Array]";
};
})(Object.prototype.toString);

Even using a closure, above function could slow down performances, specially in those libraries where Array conversions are performed almost everywhere.

As we all know, Internet Explorer behaves weird "sometimes", and one of the weirdest things is that native Objects are not instanceof Object.

In few words, instead of call a function to define if native slice could be applied, all we need to do is to check if the generic object is instanceof Object.
In this way native Arrays, arguments, everything with a length user defined, will be passed via native Array.prototype.slice, while for native objects, the good old loop will do the dirty job.

A better general purpose slice

$slice = (function(slice){
// WebReflection - Mit Style License
try {
// Chrome, FireFox, Opera, Safari, WebKit
slice.call(document.childNodes);
var $slice = slice;
} catch(e) {
// Internet Epxlorer
var $slice = function(begin, end){
// false with native objects/collections
// suitable for Array like Object (e.g arguments, jQuery, etc ...)
if(this instanceof Object)
return slice.call(this, begin || 0, end || this.length);
// ... every other case ...
for(var i = begin || 0, length = end || this.length, len = 0, result = []; i < length; ++i)
result[len++] = this[i];
return result;
};
};
return $slice;
})(Array.prototype.slice);

Simple, isn't it? And here a test case:
(function(){
try {

// HTMLCollection [object, ...]
$slice.call(document.getElementsByTagName("*"));

// arguments [3,2,1]
$slice.call(arguments);

// Array [1,2,3]
$slice.call([1,2,3]);

} catch(e) {

// should never happen
alert(e.message);
};
})(3,2,1);

where if nothing happen, simply means $slice worked without problems.

Side effects? With sandbox variables (iframes) IE will always use the loop, unless we do not bring its native Object constructor into the function (or the function into the sandbox)

Wednesday, April 1, 2009

TaskSpeed: DOM VS Libraries

I tried to create an essential library to perform a manual implementation of the TaskSpeed test suite and results surprised me.

All these web 2.0 libraries are extremely useful, cool, time-saving, whatever you want, but not a single one can compete with manual tasks implementation, specially when we are talking about the most common browser which at the same time is the slowest ever: Internet Explorer!


Final Results for an Dual Core under Windows XP SP3


FireFox 3.0.8
------------------------
library | ms
------------------------
DOM 383
plugd-a (Dojo) 734
Dojo 1.3.0 742
Dojo 1.2.3 1008
MooTools 1.2.1 1252
jQuery 1.3.2 1935
jQuery 1.2.6 3518


Safari 4 Beta (528.16)
------------------------
library | ms
------------------------
DOM 118
plugd-a (Dojo) 170
Dojo 1.3.0 186
Dojo 1.2.3 342
MooTools 1.2.1 347
jQuery 1.3.2 602
jQuery 1.2.6 1025


Opera 10.00 alpha
------------------------
library | ms
------------------------
DOM 111
Dojo 1.3.0 251
plugd-a (Dojo) 374
MooTools 1.2.1 688
Dojo 1.2.3 938
jQuery 1.3.2 1329
jQuery 1.2.6 2327


Internet Explorer 6
------------------------
library | ms
------------------------
DOM 3966
Dojo 1.2.3 22750
plugd-a (Dojo) 24189
Dojo 1.3.0 24249
jQuery 1.3.2 44314
MooTools 1.2.1 69346
jQuery 1.2.6 104407


Internet Explorer 7
------------------------
library | ms
------------------------
DOM 612
Dojo 1.2.3 3641
plugd-a (Dojo) 3748
Dojo 1.3.0 3861
jQuery 1.3.2 5031
MooTools 1.2.1 7658
jQuery 1.2.6 10468


Internet Explorer 8
------------------------
library | ms
------------------------
DOM 499
Dojo 1.3.0 2327
plugd-a (Dojo) 2455
Dojo 1.2.3 2922
jQuery 1.3.2 3170
MooTools 1.2.1 5876
jQuery 1.2.6 7049



The essential library


var $ = {
// essential stuff for TaskSpeed test by WebReflection
// Mit Style License
addEventListener:document.addEventListener?
function(name, callback, bool){
this.addEventListener(name, callback, bool);
}:
function(name, callback, bool){
this.attachEvent("on" + name, callback);
}
,
getSimple:function(selector){
for(var
split = selector.split("."),
result = [],
re = new RegExp("\\b" + split[1] + "\\b"),
list = this.getElementsByTagName(split[0] || "*"),
length = list.length,
i = 0,
j = 0,
node;
i < length; ++i
){
node = list[i];
if(re.test(node.className))
result[j++] = node;
};
return result;
},
indexOf:Array.prototype.indexOf || function(value, i){
for(var
length = this.length,
i = i < 0 ? i + length < 0 ? 0 : i + length : i || 0;
i < length && this[i] !== value;
++i
);
return length <= i ? -1 : i;
},
removeEventListener:document.removeEventListener?
function(name, callback, bool){
this.removeEventListener(name, callback, bool);
}:
function(name, callback, bool){
this.detachEvent("on" + name, callback);
}
,
slice:Array.prototype.slice
};


The test suite

// TaskSpeed, the DOM way by WebReflection
window.tests = {

"make": function(){
for(var
body = document.body,
ul = document.createElement("ul"),
li = document.createElement("li"),
i = 0,
fromcode;
i < 250; ++i
){
fromcode = ul.cloneNode(true);
fromcode.id = "setid" + i;
fromcode.className = "fromcode";
fromcode.appendChild(li.cloneNode(true)).appendChild(document.createTextNode("one"));
fromcode.appendChild(li.cloneNode(true)).appendChild(document.createTextNode("two"));
fromcode.appendChild(li.cloneNode(true)).appendChild(document.createTextNode("three"));
body.appendChild(fromcode);
};
return $.getSimple.call(body, "ul.fromcode").length;
},

"indexof" : function(){
for(var body = document.body, index = -1, i = 0; i < 20; ++i)
index = $.indexOf.call(body.getElementsByTagName("ul"), document.getElementById("setid150"));
return index;
},

"bind" : function(){
for(var callback = function(){}, li = document.body.getElementsByTagName("li"), length = li.length, i = 0, total = 0, node; i < length; ++i){
node = li[i];
if(node.parentNode.nodeName == "UL"){
++total;
$.addEventListener.call(node, "click", callback, false);
};
};
return total;
},

"attr" : function(){
for(var result = [], ul = document.body.getElementsByTagName("ul"), length = ul.length, i = 0; i < length; ++i)
result[i] = ul[i].id;
return result.length;
},

"bindattr" : function(){
for(var callback = function(){}, li = document.body.getElementsByTagName("li"), length = li.length, i = 0, total = 0, node; i < length; ++i){
node = li[i];
if(node.parentNode.nodeName == "UL"){
++total;
$.addEventListener.call(node, "mouseover", callback, false);
node.setAttribute("rel", "touched");
$.removeEventListener.call(node, "mouseover", callback, false);
};
};
return total;
},

"table": function(){
for(var
body = document.body,
table = document.createElement("table").appendChild(document.createElement("tbody")).parentNode,
tr = document.createElement("tr"),
td = document.createElement("td"),
length = 40,
i = 0,
madetable,
cell;
i < length; ++i
){
madetable = table.cloneNode(true);
madetable.className = "madetable";
cell = body.appendChild(madetable).firstChild.appendChild(tr.cloneNode(true)).appendChild(td.cloneNode(true));
cell.appendChild(document.createTextNode("first"));
cell.parentNode.insertBefore(td.cloneNode(true), cell);
};
tr = body.getElementsByTagName("tr");
length = tr.length;
i = 0;
for(var total = 0; i < length; ++i)
total += tr[i].getElementsByTagName("td").length;
return total;
},

"addanchor" : function(){
var a = document.createElement("a");
a.setAttribute("href", "http://example.com");
a.appendChild(document.createTextNode("link"));
for(var ul = $.getSimple.call(document.body, "ul.fromcode"), length = ul.length, i = 0, total = 0, childNodes, j, len, node; i < length; ++i){
childNodes = ul[i].childNodes;
j = 0;
len = childNodes.length;
while(j < len){
node = childNodes[j];
if(node.nodeName === "LI"){
++total;
node.appendChild(a.cloneNode(true));
};
++j;
};
};
return total;
},

"append": function(){
for(var div = document.createElement("div"), body = document.body, i = 0, node; i < 500; ++i){
node = div.cloneNode(true);
node.setAttribute("rel", "foo2");
body.appendChild(node);
};
for(var div = body.getElementsByTagName("div"), length = div.length, i = 0, total = 0; i < length; ++i)
total += div[i].getAttribute("rel") === "foo2";
return total;
},

"addclass-odd" : function(){
for(var div = document.body.getElementsByTagName("div"), length = div.length, i = 0, total = 0; i < length; ++i)
total += i % 2 ? !!(div[i].className += " added odd") : !(div[i].className += " added");
return total;
},

"style" : function(){
for(var div = $.getSimple.call(document.body, "div.added"), length = div.length, i = 0, style; i < length; ++i){
style = div[i].style;
style.backgroundColor = "#ededed";
style.color = "#fff";
};
return length;
},

"removeclass" : function(){
for(var re = /\s*\badded\b/g, div = $.getSimple.call(document.body, "div.added"), length = div.length, i = 0, node; i < length; ++i){
node = div[i];
node.className = node.className.replace(re, "");
};
return length;
},

"sethtml": function(){
for(var div = document.body.getElementsByTagName("div"), length = div.length, i = 0; i < length; ++i)
div.innerHTML = "<p>new content</p>";
return div.length;
},

"insertbefore" : function(){
for(var p = document.createElement("p"), ul = $.getSimple.call(document.body, "ul.fromcode"), length = ul.length, i = 0, total = 0; i < length; ++i){
for(var a = ul[i].getElementsByTagName("a"), len = a.length, j = 0, node; j < len; ++j){
++total;
node = a[j];
node.parentNode.insertBefore(p.cloneNode(true).appendChild(document.createTextNode("A Link")).parentNode, node);
};
};
return total;
},

"insertafter" : function(){
for(var p = document.createElement("p"), ul = $.getSimple.call(document.body, "ul.fromcode"), length = ul.length, i = 0, total = 0; i < length; ++i){
for(var a = ul[i].getElementsByTagName("a"), len = a.length, j = 0, node; j < len; ++j){
++total;
node = a[j];
node.parentNode.appendChild(p.cloneNode(true).appendChild(document.createTextNode("After Link")).parentNode);
};
};
return total;
},

"destroy": function(){
var result = $.getSimple.call(document.body, "ul.fromcode"),
length = result.length,
i = 0,
node;
while(i < length){
node = result[i++];
node.parentNode.removeChild(node);
};
return length;
},

"finale": function(){
var body = document.body;
while(body.firstChild)
body.removeChild(body.firstChild);
return body.getElementsByTagName("*").length;
}

};


Conclusion

I do not want to spend a word about these results, but I guess we have to think about them.