Wednesday, August 29, 2007

A better JavaScript code evaluation?

Yesterday Ajaxian published a benchmark using Flex and comparing them with Ajax JSON, Dojo and other way to transfer data between client and server.

Interesting point is not that kind of bench (imho) but one comment wrote by Albert Kühner:


A question about AJAX-JSON: Is there a performance difference if you dynamically create a script tag which loads a generated js file in which all objects are serialized to JavaScript objects (like JSON but without having to eval() the XHR response)?


It seems to be a simple and basic question but He was right because the answer is: yes, a script tag is generally interpreted faster than code evaluation using eval, execScript or Function constructor.

I suppose the reason is quite obvious: eval, as execScript or Function, brings temporary local scope inside evaluated code and this cause a little overhead while a script tag is always interpreted only inside global scope (window or super object).

This should be a limit for evaluation common usage (often wrong) but this was the primitive Ajax implementation to interact with server.

Example:

// specific callback to show product informations
function productInfoCallback(product){
alert(product);
}

// function to send something using GET way (query string)
function sendRequest(queryString, callback){
var s = document.createElement("script");
s.type = "text/javascript";
s.src = "interactor.php?".concat(queryString, "&callback=", callback);
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("*")[0])
.appendChild(s).parentNode.removeChild(s);
};

// generic product id
var productId = 123;

// interaction
sendRequest("productId=".concat(productId), "productInfoCallback");


Now try to image server side code, in this case a really simple PHP script:

if(isset($_GET['productId'], $_GET['callback'])){
$productInfo = 'Web Reflection Blog';
//$productInfo = mysql_query ...
header('Content-Type:text/javascript');
exit($_GET['callback'].'("'.addslashes($productInfo).'")');
}
?>


Well done, this is Ajax without Ajax ... a sort of Asyncronous JavaScript and JavaScript (could we call them Ajaj ? :D )


At this point You should agree that there's no way to inject temporary scope inside response and when You load manually or dynamically a script tag the behaviour is the same.

Is it ok? Is it clear?

Well, next step is to use script tag instead of code evaluation to perform quickly (I'm talking about milliseconds) expecially when We use Ajax to recieve a JSON string.

With JSON We usually don't need temporary scope so why We should "wait" more time to evaluate that string?

This is an elementary proposal, the function evalScript:

(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];


How to use them? It's really simple:

// basic example
evalScript('alert("Hello World")');


The only thing You need to remember is that scope is not injected inside evaluation:

function badEvalScript(){
var string = "something";
evalScript('var string2 = string + " else"');
};

badEvalScript();
// string is not defined
// ... and string2 will be global (window.string) if string is defined



So how to use evalScript ?
You can use them after one or more Ajax interaction if You're loading a script, instead of XML or (x)HTML or text, to run for example dinamically external resources.

You can use evalScript with JSON interactions too, but You should use a little trick if You don't want to be obtrusive.

The simplest solution is, in fact, this one:

evalScript('JSONResponse = '.concat(xhr.responseText));

... but in this web world full of different libraries this is not a good idea, so why don't use evalScript itself to store temporary results?


// generic Ajax interaction ...
evalScript('evalScript.result = '.concat(xhr.responseText));
var response = evalScript.result;


Since JavaScript is, at least inside browsers, single thread, there's no risk about concurrency and in this way You'll not overwrite anything different from single evalScript function.

If You don't believe this function is usually faster than eval, just try to generate a big and complex JSON notation, using every compatible kind of compatible value, so try 10 or more times to eval them or to use my evalScript proposal.

Of course, this is just for performances maniacs :P


Update
You can use evalScript to solve correctly global scope code evaluation too, sorry John (and jQuery team) but I suppose that eval.call(window) and setTimeout(f,0) aren't a "realistic" solution (first one requires this inside evaluation while second one, I suppose, is not syncronous).


function globalScopeEvaluation(){

var testMe = "private scope";
evalScript('alert(testMe)');

};

var testMe = "global scope";

globalScopeEvaluation(); // global scope


Finally, if You call another function to evaluate something, its scope is just global, You don't need to use eval.call or other strategies, am I wrong?


function eval2(data){eval(data)};
var a = 1;
(function(a){eval2('alert(a)')})(2);
// alert 1 ...

So I probably don't understand eval.call usage and setTimeout with data ... anyone could explain me better what does jQuery do with globalEval method? Thank You :-)

No comments:

Post a Comment