无论如何,请永远不要使用 eval 。有一个更好的选择。代替eval
,使用Function
构造函数。eval
是邪恶的,这是毫无疑问的,但是大多数人跳过了最邪恶的方面eval
:它使您可以访问本地范围内的变量。回到 90 年代,在 JIST 编译概念出现之前,这eval
听起来像是一个好主意(而且确实如此):只需将一些额外的行动态地插入到您已经逐行执行的代码中。这也意味着eval
s 并没有真正减慢一切。但是,现在使用 JIST 编译eval
语句对 JIST 编译器来说是非常繁重的,它在内部完全删除了变量名的概念。对于 JIST 编译器,为了评估 eval 语句,它必须确定其所有变量的存储位置,并将它们与在 evaled 语句中找到的未知全局变量进行匹配。如果你真正掌握技术,问题会更深。
但是,使用Function
,JIST 编译器不必进行任何昂贵的变量名查找:整个代码块是自包含的并且在全局范围内。例如,采用以下非常低效的eval
代码段。请注意,这只是为了作为示例。在生产代码中,您甚至不应该使用 eval 或Function
从内容已知的字符串生成函数。
var a = {
prop: -1
};
var k = eval('(function(b){return a.prop + b;})');
alert( k(3) ); // will alert 2
现在,让我们来看看更好的Function
选择。
var a = {
prop: -1
};
var k = (Function('a', 'b', 'return a.prop + b')).bind(undefined, a);
alert( k(3) ); // will alert 2
注意到区别了吗?有一个主要的:eval
在本地范围内Function
执行,而在全局范围内执行。
现在,进入下一个问题:安全性。有很多关于安全性有多困难的讨论,是的,使用 eval 几乎是不可能的(例如,如果您将整个代码包装在沙盒函数中,那么您所要做的就是过早地结束该函数并开始一个新的一种在当前范围内自由执行代码)。但是,使用Function
,您可以轻松(但不是最有效地)沙箱化任何东西。看下面的代码。
var whitelist = ['Math', 'Number', 'Object', 'Boolean', 'Array'];
var blacklist = Object.getOwnPropertyNames(window).filter(function(x){
return whitelist.indexOf(x) === -1 && !/^[^a-zA-Z]|\W/.test(x)
});
var listlen = blacklist.length;
var blanklist = (new Array(listlen+1)).fill(undefined);
function sandboxed_function(){
"use-strict";
blacklist.push.apply(blacklist, arguments);
blacklist[blacklist.length-1] =
'"use-strict";' + arguments[arguments.length-1];
var newFunc = Function.apply(
Function,
blacklist
);
blacklist.length = listlen;
return newFunc.bind.apply(newFunc, blanklist);
}
然后,摆弄白名单,按照你想要的方式得到它,然后你就可以sandboxed_function
像Function
. 例如:
var whitelist = ['Math', 'Number', 'Object', 'Boolean', 'Array'];
var blacklist = Object.getOwnPropertyNames(window).filter(function(x){
return whitelist.indexOf(x) === -1 && !/^[^a-zA-Z]|\W/.test(x)
});
var listlen = blacklist.length;
var blanklist = (new Array(listlen+1)).fill(undefined);
function sandboxed_function(){
"use-strict";
blacklist.push.apply(blacklist, arguments);
blacklist[blacklist.length-1] =
'"use-strict";' + arguments[arguments.length-1];
var newFunc = Function.apply(
Function,
blacklist
);
blacklist.length = listlen;
return newFunc.bind.apply(newFunc, blanklist);
}
var myfunc = sandboxed_function('return "window = " + window + "\\ndocument = " + document + "\\nBoolean = " + Boolean');
output.textContent = myfunc();
<pre id="output"></pre>
至于编写要在这个严格的沙箱下运行的代码,您可能会问,如果 window 未定义,我如何测试方法是否存在。有两种解决方案。#1 只是简单地像这样使用 typeof 。
output.textContent = 'typeof foobar = ' + typeof foobar;
<div id="output"></div>
正如您在上面的代码中看到的,使用 typeof 不会引发错误,而只会返回 undefined。第二种检查全局的主要方法是使用 try/catch 方法。
try {
if (foobar)
output.textContent = 'foobar.constructor = ' + foobar.constructor;
else
output.textContent = 'foobar.constructor = undefined';
} catch(e) {
output.textContent = 'foobar = undefined';
}
<div id="output"></div>
所以,总而言之,我希望我的代码片段能让您对 eval 更好、更好、更简洁的替代方案有所了解。我希望我已经渴望你达到一个更大的目标:冷落 eval。至于浏览器兼容性,虽然它sandboxed_function
会在 IE9 中运行,但为了让它真正沙箱化任何东西,需要 IE10+。这是因为该"use-strict"
声明对于消除许多像下面这样的偷偷摸摸的沙盒破坏方式非常重要。
var whitelist = ['Math', 'Number', 'Object', 'Boolean', 'Array'];
var blacklist = Object.getOwnPropertyNames(window).filter(function(x){
return whitelist.indexOf(x) === -1 && !/^[^a-zA-Z]|\W/.test(x)
});
var listlen = blacklist.length;
var blanklist = (new Array(listlen+1)).fill(undefined);
function sandboxed_function(){
blacklist.push.apply(blacklist, arguments);
blacklist[blacklist.length-1] =
/*'"use-strict";' +*/ arguments[arguments.length-1];
var newFunc = Function.apply(
Function,
blacklist
);
blacklist.length = listlen;
return newFunc.bind.apply(newFunc, blanklist);
}
var myfunc = sandboxed_function(`return (function(){
var snatched_window = this; // won't work in strict mode where the this
// variable doesn't need to be an object
return snatched_window;
}).call(undefined)`);
output.textContent = "Successful broke out: " + (myfunc() === window);
<pre id="output"></pre>
最后一个评论是,如果您要允许事件 API 进入您的沙盒环境,那么您必须小心:该
view
属性可以是一个窗口对象,因此您也必须删除它。还有其他几件事,但我建议彻底研究并探索 Chrome 控制台中的对象。
最后,注意这Function
是一个非常独特的构造函数,它返回一个函数而不是一个对象实例,所以不需要使用new
.