在上文《js将字符串作为函数名调用,实现input文本框等form表单元素回车键统一事件响应》中提到,因为eval()的安全性问题,建议不使用eval(),而使用其它更安全的方式实现。那么eval()到底有哪细不足,应该如何更安全地实现?
1、eval()是一个函数,看起来更像运算符1
eval()是一个函数,JavaScript早期版本定义了eval()函数,也就是从那时起,js设计者和解释器作者对其实施了更多限制,使其看起来更像运算符,原因有两点:
1)现代JavaScript解释器对eval要处理的字符串进行了大量的代码分析和优化,通常情况下,如果一个函数调用了eval(),那么解释器无法对这个函数做进一步优化;
2)eval()可以被赋予其它名称: var f = eval, g = f; 如果允许这种情况的话,那么解释器将无法放心地优化任何调用g()的函数。而当eval是一个运算符(并作为一个保留字)的时候,这种问题就可以避免掉。
2、eval()工作机制
eval()只有一个字符串参数,如果传入的不是字符串,直接返回该参数。如果参数是字符串,它会被当成JavaScript代码进行编译,编译失败抛语法错误(SyntaxError)异常,编译成功后执行代码,并返回执行结果:
1)有return的,返回return值;
2)没有return的,返回undefined;
eval()使用了调用它的变量作用域环境。简单理解就是:在函数中调用eval(),其变量作用域就是函数局部作用域,在全局中调用eval()其变量作用域就是全局作用域。看下述代码:
var foo = 1;
function test() {
var foo = 2;
eval('foo = 3');
return foo;
}
test(); //局部作用域调用,寻找和修改的变量是foo=2, 返回值 3
foo; // 1
3、全局eval()
eval()具有改变局部变量的能力,对于大部分解释器来说,当通过别名调用时,eval()会将其字符串当成顶层的全局代码执行。执行的代码可能会定义新的全局变量和全局函数,或者给全局变量赋值,但却不能使用或修改主调函数中的局部变量,看下述代码:
var foo = 1;
function test() {
var foo = 2;
var bar = eval;
bar('foo = 3');
return foo;
}
console.log("test(): ", test()); //2, 修改的是全局变量
console.log("foo: ", foo); //3
4、eval()使用限制
从上文可以看出eval确实有一定的安全风险,因此要杜绝一切外部输入的eval()运算。
5、不用eval(),使用查询window对象属性方式实现字符串函数调用
在上一篇博文中《js将字符串作为函数名调用,实现input文本框等form表单元素回车键统一事件响应》提到,使用eval()实现字符串函数调用存在一定安全风险。为此,使用在window对象中查找是否有该函数定义、找到后调用该函数替换实现,代码如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Form表单元素回车键统一响应</title>
<script src="http://res.maben.com/ly-assets/assets/js/lib/jquery-1.11.1.min.js"></script>
<link rel="stylesheet" href="http://res.maben.com/ly-assets/assets/bootstrap/css/bootstrap.min.css">
</head>
<body>
<div class="container" style="margin-top: 30px;">
<div class="form-horizontal">
<div class="form-group">
<label class="col-xs-2">Input框:</label>
<div class="col-xs-4">
<input class="form-control" id="user_name" enterKey="doSubmit" placeholder="输入姓名"></input>
</div>
</div>
<div class="form-group">
<label class="col-xs-2">Select下拉框:</label>
<div class="col-xs-4">
<select class="form-control" id="user_title" enterKey="doSubmit()">
<option value="讲师">讲师</option>
<option value="副教授">副教授</option>
<option value="教授">教授</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-xs-2 col-sm-offset-2">
<button class="btn btn-primary" type="button" onclick="doSubmit()">提交</button>
</div>
</div>
</div>
</div>
<script type="text/javascript">
function doSubmit() {
alert("name: " + $("#user_name").val() + "\ntitle: " + $("#user_title").val());
}
function evalEx(obj, exp) {
var fnName = exp.indexOf("(") >=0 ? exp.substring(0, exp.indexOf("(")) : exp;
var fn = window[fnName];
if(typeof fn === "function") {
var fnArg = exp.indexOf("(") >=0 ? exp.substring(exp.indexOf("(") + 1, exp.indexOf(")")) : "", fnArgs = [];
var strArgs = fnArg.split(",");
if(strArgs.length > 0) {
for(var i = 0, l = strArgs.length; i<l; i++) {
var strArg = strArgs[i];
if(strArg.length > 0) {
fnArgs.push(strArg == "this" ? obj || window : strArg);
}
}
}
fn.apply(null, fnArgs);
}
}
function trim(str) {
return str.replace(/(^\s*)|(\s*$)/g,"");
}
$(window).on("load",function(){
//enterKey="doSubmit"
//enterKey="doSubmit()"
//enterKey="doSubmit('keyenter')"
//enterKey="doSubmit(this)", 代码会自动替换this为事件触发对象
$("[enterKey]").each(function(i, elem){
var funcName = $(elem).attr("enterKey");
if(! funcName) return false;
$(elem).bind("keydown", function(e){
if(e.keyCode == 13) {
evalEx(e.target, funcName);
}
});
});
});
</script>
</body>
</html>
JavaScript权威指南 第6版. ↩︎