Thanks for all the ideas, I ended up just doing text replacement in the build script that outputs the JS, basically replacing $EVAL$ with eval, after everything has been compressed. I was hoping for a purely JS way, but with so many different eval browser implementations, it's probably better to just leave eval alone
But based on Dimitar's answer and some fiddling around, here's what I found.
Seems like the reason why this['eval'] wasn't work is because the place where it's happening, in MooTools JSON.decode, is also a inside a Hash:
var JSON = new Hash({
// snip snip
decode: function(string, secure){
if ($type(string) != 'string' || !string.length) return null;
if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null;
return this.eval('(' + string + ')'); // Firefox says: TypeError: this.eval is not a function
}
});
However, if I store the "top level" local scope (all the code, including mootools, runs inside an anonymous function), then it works:
var TOP = this;
var JSON = new Hash({
// snip snip
decode: function(string, secure){
if ($type(string) != 'string' || !string.length) return null;
if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null;
return TOP.eval('(' + string + ')'); // All good, things run within the desired scope.
}
});
However this doesn't work in Safari, so bottom line is, what I was trying to do can't be done cross-compatibly. eval is a special touchy function and every browser treats it differently.