scope and eval explanation
I have an Ajax call where I get back some JavaScript as a String. In the onSuccess Method I want to eval this code. In the JavaScript code there are Function-declarations. All these function should be accessible after the eval.
I made up a small-as-possible example. (The things are going on in the onFailure method in the example, because in JFiddle I can't make a successfull Ajax Call).
You can find the Example here: http://jsfiddle.net/ubXAV/6/
The example you see is working in all browsers (Unfortunately, this will not work in JSFiddle in IE). I marked some lines refering to questions below. Here's the code again:
function evalScript(script)
{
that.eval(script); //1.
}
var that = this;
// AJAX-Call - GadgetActionServlet
new Ajax.Request("THISWILLFAIL.com", {
method: 'post',
onSuccess: function(ajaxResponse) {
alert("success");
},
onFailure: function(){
var script = "{function sayHello(){alert('Hello');}}";
//that.eval(script); //not working in IE 2.
evalScript(script); //working in all browsers
}
});
I read a lot in the internet about scopes and contexts in java but i just can't explain the behaviour here:
Why do I need to call eval on "that" ? According to many sources 开发者_如何学编程on the internet the context of a globally defined function is the most global context. (Here it should be window). And the code evaluated through eval should be executed in the context which is calling the eval function.
Assuming, that there is a new global context for the Ajax call (is it?) why can i access the evalScript function but not evaluate the script here directly.
The overall question i have is: Which particular rules apply to the usage of eval? Where are my functions attached to regarding the context? And: does an prototype Ajax call like in the example has its own global object?
First off: If you can avoid using eval
, avoid using eval
. Does your code have to come back from a POST
? Because if you're willing to use GET
instead, you can just append a script element to the page:
var script = document.createElement('script');
script.src = "http://example.com" +
"?" + encodeURIComponent("param1name") + "=" + encodeURIComponent("param1value") +
"&" + encodeURIComponent("param1name") + "=" + encodeURIComponent("param2value");
var parent = document.body
|| document.documentElement
|| document.getElementsByTagName('head')[0];
parent.appendChild(script);
Done.
Or if it has to be POST
, does it really have to be actual script code? Couldn't it be data that's interpreted by code already on the page? JSON is a useful data format if you can go that way.
But if it has to be POST
, and what you get back has to be actual script code as opposed to data, then we'll have to do something like eval
. :-)
eval
itself is very, very special. It works within the scope in which it's used, even though it looks a bit like a function and that's not how functions work. So actually evaluating script code in global scope is hard unless the eval
call is actually at global scope (not within any function call), and of course you can't do that here — you have to trigger this from your ajax callback, and so by definition this happens within a function. (Edit: I just thought of a way to actually use eval
at global scope, from within a function. See the update at the end of the answer. But it's evil and horrible and wrong.)
The reason you may have seen advice saying to use window.eval
is that a lot of modern browsers offer window.eval
(as opposed to eval
) which evaluates the given code in global scope. But it's not available on all browsers, and certainly not older ones.
There are workarounds, though. The IE family provides execScript
which is very similar to the window.eval
offered by other browsers, and in the worst case you can fall back on using a script
element. Here's a global eval function that works in nearly everything:
window.evalInGlobalScope = (function() {
var fname, scr;
// Get a unique function name
do {
fname = "__eval_in_global_test_" + Math.floor(Math.random() * 100000);
}
while (typeof window[fname] !== 'undefined');
// Create test script
scr = "function " + fname + "() { }";
// Return the first function that works:
return test(evalInGlobalScope_execScript) ||
test(evalInGlobalScope_windowEval) ||
test(evalInGlobalScope_theHardWay) ||
evalInGlobalScope_fail;
function test(f) {
try {
f(scr);
if (typeof window[fname] === 'function') {
return f;
}
}
catch (e) {
return false;
}
finally {
try { delete window[fname]; } catch (e) { window[fname] = undefined; }
}
}
function evalInGlobalScope_execScript(str) {
window.execScript(str);
}
function evalInGlobalScope_windowEval(str) {
window.eval(str);
}
function evalInGlobalScope_theHardWay(str) {
var parent, script, d = document;
parent = d.body || d.documentElement || d.getElementsByTagName('head')[0];
if (parent) {
script = d.createElement('script');
script.appendChild(d.createTextNode(str));
parent.appendChild(script);
}
}
function evalInGlobalScope_fail() {
throw "evalInGlobalScope: Unable to determine how to do global eval in this environment";
}
})();
..and here's a live example of using it.
Note that all of the code figuring out what to use only runs once; the function that got chosen is assigned to the evalInGlobalScope
property on window
.
Also note that I haven't given it any return value. That's because the "hard way" version basically can't return any return value, so it's safest if none of them does. Mind you, I'm not sure what browsers still require "the hard way" — nearly everything has execScript
and/or window.eval
now.
Update: I said above that you couldn't use eval
at global scope from within a function. And technically that's true, but I thought of a way to do an end-run around it. It's evil and horrible and wrong, but it does work: Use setTimeout
instead, and give it a timeout of 0
:
setTimeout("your code here", 0);
When you give setTimeout
a string, it performs an eval
on it — after the timeout, at global scope.
Again, it's evil and horrible and wrong, and it has the added disadvantage that it's asynchronous (whereas with our evalInGlobalScope
function, the eval happens synchronously), but it does...sort of...work. (Live copy) I do not recommend it.
精彩评论