什么是eval的直接调用?

更新日期: 2018-11-24阅读: 1.7k标签: eval

ES5设计直接调用的目的就是, 让eval 有改变eval动态执行代码的scope 为global object的这一能力.  但是很不好的是.他们通过直接调用来实现这种,很抽象的概念来实现这个能力. 而后面那些官方咒语般的描述的本质,其实是想说明一层意思 . 就是 , 如果你的语句里 ,eval 是一个看起来独立调用, 不转借它人, 对 eval的使用,好像一个关键字来用的那种感觉.就算作直接调用,那么eval内的动态执行的代码,就如同是执行在eval被调用的函数内,而对应的非直接调用则如果global code一样.(直接体现作用域的影响).但是这种含义,太难描写了. 所以 ES5蛋疼的出现了下面的那种定义... 好吧. 如果你觉得看到这里就解决了你的困惑, 我就十分赞成,你放弃后面部分的阅读...直接离开了. 如果你非想问个为什么...那就继续看吧...  


解释前的准备:
在描述前要简单介绍下原文中提到的两个词法非终结符.MemberExpression , CallExpression. 
CallExpression , 你姑且认为 fn('Franky') 就是一个CallExpression,但其实 fn('Franky') 只是符合CallExpression的一种情况,即 MemberExpression Arguments 这一个语法产生式,你姑且看成fn对应MemberExpression ,('Franky')对应Arguments.

词法、语法相关知识不是本文重点....就此打住.

简单来说, ES5定义的 eval的调用 要满足下面俩条件,则被视为 直接调用:

1.解释执行CallExpression中MemberExpression的结果,必须是一个,其base值为一个环境记录(environment record),且 referenced name为 "eval" 的引用类型.
2.把步骤1的结果作为参数,进行GetValue抽象运算的结果,必须就是内置方法 eval.

 

名词解释:

environment record(环境记录):

有两种环境记录值.被ES5所定义.分别是:声明式环境记录(declarative environment records)对象环境记录(object environment records).声明式记录是用于定义ECMAscript语言的,语法元素中如 FunctionDeclarations,VariableDeclarations,和Catch子句与ECMAScript语言值,直接关联的标识符绑定特性. 对象环境记录则是用于定义ProgramWithStatment这样的.某对象属性上发生的标识符绑定.  要解释清楚这两种东西. 需要更加庞大的篇幅,所以本文就不再做展开的说明了. 你姑且把环境记录,看做ES3中的 变量对象. 它的作用就是维护标识符,比如 变量声明, 函数声明, 函数的形式参数.等. ES5只是把 变量对象更加细化的分为两个类型.分别用于说明不同场景的标识符的维护方式的差异. 


Reference Type(引用类型):

引用类型,不是语言层面的数据类型. ECMA262中定义它,是为了更好的对规范进行解释说明. 但是ECMAScript的实现.必须在内部操作中,参考此处的描述. 引用类型的值仅仅用来作为表达式运算的中间结果.并不能用来给一个变量或属性持有. 
引用类型是设计用来解释一些如 delete、typeof、=(赋值)  等运算符的行为的. 即解释执行(evaluate)语句、表达式、以及 GetValue的求值过程.
举个例子: 赋值运算的行为可以使 "=" 赋值运算符左边的运算元(左值表达式).预期运算结果为一个"引用". (这并不是说 variable = 'Franky'; 这种赋值语句的结果是引用类型,而是指对variable 左值表达式的解释执行的中间过程,产生一个引用类型用于后面的赋值运算.)

ES5 :由 base value, referenced name(同edition3的 property name), 以及一个叫做 strict reference 的布尔值(用于严格模式).构成的一个内部对象.这货其实就是描述, 某个对象的 叫做xxx的属性. 对其进行 GetValue 运算,就会调用该对象的内部方法[[Get]] 来获取该属性的值.那么 base有可能是一个对象,一个字符串,一个数字,一个布尔值,或undefined, 还有一个就是我们关心的被ES5 称为 environment record(环境记录,对应ES3的变量对象)的东西


别奇怪为啥base可以是字符串什么的(这是ES5与ES3的一个区别之处)

参考 -  'Franky'.length  语句按照ES5的的描述,其解释执行过程大概是这样子:

1. 先把'Franky'.length 转换为 'Franky'['length']
2. 解释执行 'Franky'
3. 对 步骤2的结果,进行GetValue运算(对解释执行的结果求值的抽象运算,运算元可以是一个原始值,或一个引用类型,好吧又绕回去Reference Type了)
4. 解释执行'length'
5. 对步骤4的结果进行GetValueu运算
6. 对步骤3的结果进行 ToObject运算(尝试进行装箱操作.参考new String('Franky'))
7. 对步骤5进行ToString运算(因为'length' 这部分可能是一个表达式,所以要先解释执行,然后求值,然后再尝试转为字符串.才可能符合属性访问器的语法)
8. 返回一个 base值 为步骤5的结果(ES3则是步骤6的,这也是为啥ES5 的base,可以是各种奇葩原始值的原因之一..). referenced name 为步骤7结果, strict reference 为代表当前执行环境是否为严格模式的布尔值. 的引用类型

好了,后面如何返回字符串Franky的字符数的,不属于本文讨论范围(相信你见到new String('Franky')的装箱过程,应该想得到...).

而上面这个解释执行过程,ES中被称为属性访问器( Property Accessors) .这个属性访问环节,总是要返回一个reference type.然后再进行后面的操作.

列出这个过程,只是想让不熟悉ES的朋友,对ES的 Reference Type,有个直观的感受.


GetValue:

你姑且认为他就是对一个引用类型 求值的过程.因为引用类型实际作用是对 某对象上某个属性的一种描述.所以本质是对就是获取该对象某个属性值过程(涉及到对象的内部方法[[Get]],不再详细介绍). 当然内部过程不是如此简单. 比如reference中,base 值为 
undefined,123,等等.
而当base值为一个环境记录时,意味着这个引用类型其实是一个被称为 Identifier Reference的东西. 比如 对 var abc = 123; 中 abc 进行解释执行的过程,会得到一个标识符引用.就是这个意思.

有了上面这些基础后,我们再回过头来看demo.

;(function(){
  var a = 1;
  var fn = eval;
  eval('typeof a'); //number 
  (eval)('typeof a');//number
  (1,eval)('typeof a');//undefined
  fn('typeof a');//undefined
}());

语句 eval('typeof a') :
符合两个条件- eval 作为callExpression中的MemberExpression部分,本质上是一个标识符解析,返回一个Identifier Reference(标识符引用). 其base value 即是充当 environment record 的 global object. 而 referenced name显然就"eval".


语句 (eval)('typeof a') :
符合两个条件- 原因上一篇提到过,( xxx ) 中分组运算符,在生成抽象语法树时,只是影响语法树生成过程,或者说是影响其他元素在语法树中的位置和顺序(从程序运行角度来说,本质是对运算优先级的影响),但不会作为节点保留在语法树中.也就是在运行时,不会 有额外的与其有关的运算.所以之前我会说它在语法分析期.尽到了义务后,就被消除了.(此处罗嗦的解释一下,只因为,昨天有朋友表示之前的简单描述,无法解决他的困扰.). 那么这里的情况其实就和第一个demo是一样的了.


语句 (1,eval)('typeof a')

不符合条件1中,结果为 Reference Type这一要素. (1,eval) 中分组运算符,虽然同demo中一样,被消掉. 但是 1,eval 这个表达式 实际的含义是 "," 逗号运算, 即 1和 eval 都是逗号运算符的运算元. 其运算结果为对,eval解释执行后获得的reference 进行GetValue的结果. 显然因为多了一次GetValue 导致结果不符合这个条件了.


语句 fn('typeof a')

不符合条件1 中referenced name为 "eval" ....


下面我要额外说的是一个 特殊的东西.

语句 window.eval('typeof a') //undefined .

为什么要额外补充这个呢,因为其比较特殊. 首先,它 不符合的是 其base值为一个环境记录(environment record) .这一点,对于熟悉ES5的朋友,可能会比较困惑..因为:

1. window,在ES层面等价global (虽然绝大多数浏览器的实现,并非如此),参考 :http://www.cnblogs.com/_franky/archive/2010/12/20/1911328.html

2. global object 在某些情况下,是作为environment record 存在的.

3. window.eval  , 这种被称为 Property Accessors 的东西.解释执行的结果,就总是一个 base value 为 GetValue(evalute(window)) ,referenced name 为 "eval" 的一个 引用.  而对这个引用进行GetValue运算得到的结果又一定是 内置方法 eval (除非有人劫持了他们). 


那么看起来window.eval似乎符合 两个条件啊.. 其实不然. 设计上, environment record 对外是不可见的. 从window访问global时, 显然算做可见的访问. 而不是通过词法环境(Lexical Environments, 好吧又一个新名词.对应ES3 的作用域链的实现机制.前面提到的environment record就是挂在Lexical Environments下面)来找的. 只有发生类似作用域链中查找标识符情况.找到global时它才是environment record. 才会满足条件. 这也是为啥 在任何执行环境(很深的内层函数嵌套中) eval('xxx') 最终找到global.eval.但是却算作直接调用的原因.

注: 应该注意那些,按照ES3标准实现的引擎的行为. 与本篇描述不符. 比如悲剧的IE8- 的jscript引擎.和其他主流浏览器的部分早期版本.

 

好吧.到了此篇该结束的时候了. 最后送上一个小demo .你能不测试就说出结果么?

;(function (eval) {
  var a = 'franky';
  eval('alert(typeof a)');
})(eval);

原文:https://www.cnblogs.com/_franky/archive/2012/08/18/2645024.html

链接: https://www.fly63.com/article/detial/6611

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!