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(环境记录):
Reference Type(引用类型):
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
现在很多网站都上了各种前端反爬手段,无论手段如何,最重要的是要把包含反爬手段的前端javascript代码加密隐藏起来,然后在运行时实时解密动态执行。动态执行js代码无非两种方法,即eval和Function。
toString() valueOf()比较会隐式调用toString或者valueOf方法,如果原始类型的值和对象比较,对象会转为原始类型的值,再进行比较。array.join = array.shift数组也是对象,数组的toString 方法返回一个字符串
JavaScript函数是被设计为执行特定任务的代码块。JavaScript函数会在某代码调用它时被执行。JavaScript中eval()函数可计算某个字符串,并执行其中的的JavaScript代码。
eval()函数就是一个完整的 ECMAScript 解释器,它接收一个参数,即一个要执行的 ECMAScript(JavaScript)字符串。与很多解释型语言一样,JavaScript有能力解释JavaScript源代码字符串
Global Object代表一个全局对象,js中不允许存在独立的函数,变量和常量,它们都是Global Object 的属性和方法,包括内置的属性和方法,但是Global Object实际并不存在,它是由window充当这个角色,并且这个过程是在js首次加载时进行的
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!