Today, I ran some tests to help me understand the scope in which an eval runs. Turns out, like so many things in the browser world, it's very unpredictable and exhibit different behaviors in different browsers.
Let's start with the following snippet of code. I've added comments to demarcate areas in the code, which I will be changing with each iteration.
var foo = 123;
var bar = {
changeFoo: function() {
// We'll keep changing the following snippet
alert(this);
eval("var foo = 456");
// Changing snippet ends
}
};
bar.changeFoo();
alert(foo);
A little explanation of the code above. foo
is a variable in the global scope, and it's value is set to 123. An object bar
is created with a single method changeFoo
which does an eval
. The eval
creates a local variable (thanks to the var
) foo
, and sets it's value to 456. bar.changeFoo
is called, and the value of the global foo
is alert
ed.
The aim is to test the scope in which eval
runs. If eval
is in the global scope, the global variable foo
should change it's value. If eval
is in the local scope, the global foo
should be unaffected. Then there are various things we can do inside the changeFoo
method which should keep altering the scope of this
, so we are also alerting this
to see what happens.
The findings are listed below:
Changed snippet | Internet Explorer | Safari 3.x | Firefox | Google Chrome | Safari Nightlies | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|
foo | this | foo | this | foo | this | foo | this | foo | this | ||
1 |
| 123 | object | 123 | object | 123 | object | 123 | object | 123 | object |
2 |
| 123 | object | 123 | object | 456 | object | 123 | object | 456 | object |
3 |
| error | object | error | object | error | object | error | object | error | object |
4 |
| 123 | object | 123 | object | 456 | object | 123 | object | 123 | object |
5 |
| 123 | object | 123 | window | 123 | window | 123 | object | 123 | window |
6 |
| 123 | object | 123 | window | 456 | window | 123 | object | 456 | window |
7 |
| 456 | object | 456 | object | 456 | object | 456 | object | 456 | object |
8 |
| 456 | object | 456 | object | 456 | object | 456 | object | 456 | object |
What I think of these results:
- I don't know what Firefox is doing in case 2, and for some reason Safari Nightlies seem to be following it. Maybe it's just beyond my understanding, but case 2 is not supposed to be different from case 1. Why does case 2 operate in global scope? If
window.eval
is different fromeval
, case 3 shouldn't all have given errors. Someone please help me understand that $hit. - Case 4 makes sense, but that's a non-standard behavior in Firefox. Understandable that no one else exhibits it.
- IE baffles me in case 5, and Chrome seems to ape it. In this scenario, the anonymous function is supposed to have the global scope - so, in this case,
this
should point to the window. WTF is happening here! - Consistent with case 2 above, Firefox and Safari Nightlies continue to display weird behavior in case 6. For some reason, in these two cases, the
eval
operates in the global scope. - Now, I have no idea why, but only cases 8 and 9 seem to really work at all. This is despite Doug Crockford going on and on about not using
with
constructs. It's also despite being beyond (my) understanding about why thewith
should make any difference to theeval
, sinceeval
is part of the window object.
All in all, if you are going to be eval
ing JavaScript (not JSON), and you want the eval'd code to run in the global scope, you should use the with
block around the JavaScript snippet. Or else, you can lose a lot of hair handling cross-browser issues.
Hope you don't lose as much hair as me.