1. 程式人生 > >不用 eval 用查詢window物件屬性方式實現字串函式呼叫

不用 eval 用查詢window物件屬性方式實現字串函式呼叫

  在上文《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>

  1. JavaScript權威指南 第6版. ↩︎