javascript eval/window.eval/window.execScript区别/用法/要点

在群里看到耗子说了些关于indirect eval call,觉得好奇,于是便查了一下,本身以为自己对eval以及足够了解,可惜偶还是井底之蛙啊,没事,偶跟各位好奇的看客一样,让咱揭开面纱。

首先新人大大脑补,老手无视。eval是干啥的?eval是直接将一段字符串作为参数,交给JS引擎预编译器进行动态分析并执行代码。如下:

  1. //小熊调试台输出,你可以理解为console.log,再不理解就理解成alert也没事
  2. var info = log.info;
  3. eval('info("a")'); //打印字母"a"

结果:
PS:该调试台为小熊二次开发的一个插件,如果你觉得感兴趣,可以看看小熊这篇文章

额 - -,为表示符合用户体验,偶应该让新人大大们懂得其用途用于哪里和我们有哪些需要注意的地方。

啥时候用?(高手请飘过)

当我们需要执行一个未知,动态变化的代码时可能会派上用场(其实这种机会也挺少见的),见一下代码:

  1. function fn1(){   
  2.     //...你要执行的代码
  3. }   
  4. function fn2(){   
  5.     //...你要执行另一些代码
  6. }   
  7.   
  8. var Ineed = "fn1";   
  9. eval(Ineed + '()'); //运行fn1();   
  10.   
  11. Ineed = 'fn2';   
  12. eval(Ineed + '()'); //运行fn2();

有上述代码可见,eval是一个用于未知性,作为动态型的需求时而用来解析代码的函数,另外,eval会在执行上下文作出新的活动对象,所有内部解析的代码,将会直接推入作用域链的顶端,例如:

  1. var info = log.info, //小熊调试台输出   
  2. where = '我在国外'//全局作用域的where   
  3. function test(){   
  4.     eval('var where = "我在国内"');   
  5.     //动态创建的 where 属于闭包的where(新人大大可以理解为在test内 var where = '我在国内')   
  6.   
  7.     info(where); //所有浏览器表示 '我在国内'   
  8.     //注意因为作用域链的搜索是从自身函数逐级回溯到全局作用域的,所以这里的where是闭包内部的where   
  9. }   
  10. test();  

所以,eval存在一定性能问题,特别是在双重运算上(eval在传字符串还带了变量与字符串做连接),在标识符搜索过程中存在一定安全隐患,如果不是特别需求以及清除其威力,请尽量少用eval。如果未能理解作用域的问题,请自行google javascript闭包。

区分eval/window.eval

接着我将讲一下在ECMA-262标准中,表示对eval及window.eval做出了区分,偶心想,eval 能跟 window.eval有啥区别嘛?搞的头绪乱乱的,于是首先咱来个示例代码,让我们明白其明显区别:

  1. var info = log.info, //小熊调试台输出   
  2. where = '我在国外'; //全局作用域的where   
  3. function test(){   
  4.     var where = '我在国内'; //闭包的where   
  5.     eval('info(where)'); //我在国内   
  6.     window.eval('info(where)'); //我在国外 IE6/7/8没出国   
  7. }   
  8. test();

FF
IE6(表示最大兼顾化,这里以IE6作为IE经典形象大使登台,其输出结果在IE6/7/8三个下保持一致,IE9与FF结果相同)

以ECMA标准(FF结果)而论我们可以得出以下结论:

  1. eval是从本身函数作用域内进行标识符搜索(由于eval实现是动态创建活动对象推至执行环境顶端,则搜索标识符的方式就按照平常作用域逐步回溯的方式进行)。
  2. 而window.eval是将参数内部的str从全局作用域变量进行标识符搜索,我们可以理解为:

    • eval => eval.call(this, evalStr)
    • window.eval => eval.call(window, evalStr)

嗯嗯,知道有人要说了,这玩意儿IE6/7/8不顶用咱拿他干啥?嗯,可怜的IE6/7/8有个方法派上用场,这里码上:

  1. var info = log.info, //小熊调试台输出   
  2. where = '我在国外'//全局作用域的where   
  3. function test(){   
  4.     var where = '我在国内'//闭包的where   
  5.     OldIE = !-[1,]; //经典的最短IE怪癖检测,现在IE9已经fixed了   
  6.     eval('info(where)'); //我在国内   
  7.     if(OldIE){ //IE判断   
  8.         window.execScript('info(where)'); //我在国外   
  9.     } else {   
  10.         window.eval('info(where)'); //我在国外   
  11.     }   
  12. }   
  13. test();  

FF
IE6

接下来我在demo的途中发现个怪事儿,大家请看:

  1. var info = log.info, //小熊调试台输出   
  2. where = '我在国外'//全局作用域的where   
  3. function test(){   
  4.     var where = '我在国内'//闭包的where   
  5.     evalClone = eval; //eval引用传递   
  6.   
  7.     //默认eval 测试   
  8.     eval('info(where)'); //全体在国内   
  9.   
  10.     //eval引用传递的eval 测试   
  11.     evalClone('info(where)'); //标准们出国 OldIE(IE6/7/8)在国内'   
  12.   
  13.     //判断相等   
  14.     info(eval == evalClone); //全体 true   
  15. }   
  16. test();  

FF
IE6

IE6结果没啥奇怪的,本身还未跟上潮流路线的IE6/7/8理所当然认为eval跟window.eval是一样的,我们就此讨论FF的结果,应该很多人觉得很怪,第一次“国内”好理解(上方第8行),第二却是“国外”(上方第11行,调用全局域的where去了)。同时bool的全等判断为true(上方第14行),左思右想,我打算再加一个window.eval的引用传递:

  1. var info = log.info, //小熊调试台输出   
  2. where = '我在国外'//全局作用域的where   
  3. function test(){   
  4.     var where = '我在国内'//闭包的where   
  5.     evalClone = eval, //eval引用传递   
  6.     evalGlobal = window.eval; //window.eval引用传递   
  7.   
  8.     //默认eval 测试   
  9.     eval('info(where)'); //全体在国内   
  10.   
  11.     //eval引用传递的eval 测试   
  12.     evalClone('info(where)'); //标准们出国 OldIE在国内'   
  13.   
  14.     //window.eval引用传递的eval 测试   
  15.     evalGlobal('info(where)'); //标准们出国 OldIE在国内'   
  16. }   
  17. test();  

FF
IE6

这次第二、三都在“国外”(对应12、15行)。于是,我推测,eval类似属于每个函数内部本身可以执行的一种“自带”函数,直接调用的eval就是eval而非window.eval,而通过值转化的eval(例如值的返回)会被转成window.eval,为证实,用逗号表达式将会更明显:

  1. var info = log.info, //小熊调试台输出   
  2. where = '我在国外'//全局作用域的where   
  3. function test(){   
  4.     var where = '我在国内'//闭包的where   
  5.     evalClone = eval, //eval引用传递   
  6.     evalGlobal = window.eval; //window.eval引用传递   
  7.   
  8.     //默认eval 测试   
  9.     eval('info(where)'); //全体在国内   
  10.   
  11.     //eval引用传递的eval 测试   
  12.     evalClone('info(where)'); //标准们出国 OldIE在国内'   
  13.   
  14.     //window.eval引用传递的eval 测试   
  15.     evalGlobal('info(where)'); //标准们出国 OldIE在国内'   
  16.   
  17.     //逗号表达式,保证以返回形式获取eval 测试   
  18.     (1,eval)('info(where)'); //标准们出国 OldIE在国内'   
  19. }   
  20. test();  

FF
IE6 (本身还未跟上ES标准,IE6/7/8全体不出国,这里节省请求不放图)

由FF结果说明,一旦使用了值的转换(18行代码相当显眼),则使用的方法是window.eval而非eval,另外上面的粗体总结用了‘eval类似属于每个函数内部本身可以执行的一种“自带”函数’,这句话,这里只是比喻,自带并不自带,如果我们这样,eval就会被替换:

  1. var info = log.info, //小熊调试台输出   
  2. where = '我在国外'//全局作用域的where   
  3. eval = alert; //eval被整容了 - -   
  4. function test(){   
  5.     var where = '我在国内'//闭包的where   
  6.     eval('info(where)'); //浏览器自带弹窗,内容'info(where)'   
  7. }   
  8. test();  

FF
IE6一样也是弹窗,可以直接参考上图。

于是我们可以总结:

  1. eval 的作用域建立在自己的所属环境基础上
  2. window.eval的作用域是在全局上
  3. eval类似属于每个函数内部本身可以执行的一种“自带”而又可以被重写的函数(实在理解不了,可以把直接书写eval的函数理解为this一样的存在)
  4. 直接调用的eval就是eval而非window.eval(franky的说法是:eval 就是看 是不是直接调用,window.eval 不是直接调用 所以是global execution context),而通过值转化的eval(例如值的返回)会被转成window.eval的方法。

另感谢js罗浮宫2群,银川-bird给出了扩展资料,其中也证明了这点:

这里给需要的亲(E文无障碍的话)可以狠狠点击这里

  1. avatar
    bird#119

    楼主真有心哈~~

    • avatar
      ibearB1

      @bird
      哥,你来了吖~~~

  1. 目前还没有trackbacks.

  2. Trackbacks被禁用了

发表评论 进楼快捷键: ctrl+Enter取消回复

电子邮件地址不会被公开。

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>