Thursday, August 30, 2007

global scope evaluation and DOM investigation

After precedent post I prefer to write another one about global scope evaluation and kentaromiura question:

does your evalScript proposal cause DOM side effects?


Before I post my test code that shows how a medium or big page, about 5000 divs or paragraphs, is not affected by my proposal (about 0.09 delay over 1000 evalScript interactions) I would talk about global scope evaluation.

What does it mean: global scope evaluation?
Every time we use a function to evaluate a string, temporary scope is injected into code evaluation.

While some library (in this case I talk about jQuery) uses stranges (imho) partial (wrong) solutions to remove nested scope from evaluation, I can tell You that everything You need to use a clear scope is to call another function, where this reference is just super object (window if executed inside a browser).

This is an elementary example:

function clearScopeEvaluation(data){
return eval(data);
};

// just call them everywhere ...
(function(a){
clearScopeEvaluation("a = 2");
alert([a, window.a]);
})(1);

// shows 1,2


If You use a setInterval or setTimeout function with delay === 0, You're not solving the problem because these functions are always asyncronous.
This mean that this example code will show 1 in every "delay 0" compatible browser (Opera doesn't accept 0 as delay):

var num = 1;
setTimeout(function(){num = 2}, 0);
alert(num);

// ... or if You prefere ...
var num = 1;
setTimeout("num = 2", 0);
alert(num);

That's why when You use timing or Ajax, callback will be executed at the end of every line of code, only modal events such alert, prompt or others will cause delayed function execution, if time was short enough.


// just click OK before one second or wait more to view 2 value
var num = 1;
setTimeout(function(){num = 2}, 1000);
alert("");
alert(num);

To understand better delayed functions, please read this post that shows a really basic deadlock ... just why JavaScript, if executed inside a browser, is not multi threaded.

John Resig (jQuery author) seems to know it, He implemented these functions (setTimeout and setInterval) inside Rhino (sorry John, I don't find the link) ... but remember that His implementation is based on Java threading and not on JavaScript.

For some reason there's a function inside jQery that I can't understand, called globalEval:

// Evalulates a script in a global context
// Evaluates Async. in Safari 2 :-(
globalEval: function( data ) {
data = jQuery.trim( data );
if ( data ) {
if ( window.execScript )
window.execScript( data );
else if ( jQuery.browser.safari )
// safari doesn't provide a synchronous global eval
window.setTimeout( data, 0 );
else
eval.call( window, data );
}
}

Too many concept errors for a single function ... I hope jQuery team will remove or change them as soon as they can.

Returning on simple "free scope" evaluation, there's always a limit: arguments object or sent variables should be changed by code evaluation.

function gScope(data){
eval("data=arguments=null");
};


Since global scope evaluation should be done exactly using global scope, as script tags do, my evalScript proposal does it quite perfectly and above example will not change, using my function, recieved data or arguments variables.

For people who think that code evaluation is an obsession I can just say that my evalScript proposal was wrote to load entire scripts, as dynamic appendChild(scriptWithExtSource) does, or to evaluate a bit faster big JSON server responses.

In these cases You can't say eval is evil because there aren't different way to do that if You care about speed and dynamic script import.

This is my last sentences about code evaluation, it's always a bad practice but in rare cases, it's a must.

To complete this post, here You can see my benchmark code to test evalScript speed after created a big page (big DOM):

(evalScript = function(e){
var h = evalScript.node,
s = document.createElement("script");
s.type = "text/javascript";
s.text = e;
h.appendChild(s);
h.removeChild(s);
}).node = document.getElementsByTagName("head")[0] || document.getElementsByTagName("*")[0];

onload = function(){

function create(element, content){
document.body.appendChild(
document.createElement(element)
.appendChild(
document.createTextNode(content)
).parentNode
);
};

function randomString(){
var i = Math.ceil(Math.random() * 200),
a = new Array(i);
while(i--)
a[i] = String.fromCharCode(Math.ceil(Math.random() * 40) + 30);
return a.join("");
};


for(var i = 0; i < 5000; i++)
create(i % 2 ? "div" : "p", randomString());

for(var i = 0, j = 0; i < 1000; i++){
startTime = new Date;
evalScript('endTime = new Date - startTime');
j += endTime;
};

alert(j / 1000);

};

No comments:

Post a Comment