首页 > 代码库 > this, eval('this'), (0, eval)('this'))
this, eval('this'), (0, eval)('this'))
今天又被一段javascript代码神奇到了
function test() { console.log(this, eval(‘this‘), (0, eval)(‘this‘)); };var a = {};test.apply(a);
在现代浏览器的console里跑上边的代码,结果应该类似是下边这样
Object {} Object {} Window {top: Window, window: Window, location: Location, external: Object, chrome: Object…}
this和eval(‘this‘)的结果是Object {},也就是a。而(0, eval)(‘this‘)的结果就变成了window,也就是全局环境里的this。
为什么呢?
解释这段代码其实需要梳理两个概念
1. this是什么
2. eval的scope
Javascript方程的四种调用模式
首先简单介绍下这部分知识,我是从JavaScript: The Good Parts里读的,这书真是看过的都说好。
JavaScript里调用函数时除了函数声明里的参数外还会外加一个参数this,而this的值取决于调用函数的方式。
四种调用方式分别是:
1. 方法调用(Method Invocation Pattern):函数为一个对象的属性,比如myObj.functionName(param),this为该对象(myObj)
2. 函数调用(Function Invocation Pattern):像functionName(param)这样的调用模式,this为全局对象
3. 构造函数调用(Constructor Invoation Pattern): 比较特殊的一种,就是new ObjConstructor(param)这类的,this为新建的那个新对象
4. apply调用(Apply Invocation Pattern):也就是我们一开始的代码的test.apply(a)里这种,this为apply的参数数组里的第一个,在这里也就是a
弄懂了这些,我们知道test.apply(a)调用的test里,this应该就是a。但是为什么最后一个(0, eval)(‘this‘)输出的不是a呢?
ECMAScript 5里对eval的规范
首先引用几条百度来的ECMAScript5.1规范中文版
10.4.2 进入eval代码
当控制流进入 eval 代码 的执行环境时,执行以下步骤:
- 如果没有调用环境,或者 eval 代码 并非通过直接调用(15.1.2.1.1)eval 函数进行评估的,则
- 按(10.4.1.1)描述的初始化全局执行环境的方案,以 eval 代码 作为 C 来初始化执行环境。
- 否则
- 将 this 绑定设置为当前执行环境下的 this 绑定。
- 将词法环境设置为当前执行环境下的 词法环境 。
- 将变量环境设置为当前执行环境下的变量环境。
- 如果 eval 代码 是 严格模式下的代码 ,则
- 令 strictVarEnv 为以词法环境为参数调用 NewDeclarativeEnvironment 得到的结果。
- 设置词法环境为 strictVarEnv。
- 设置变量环境为 strictVarEnv。
- 按 10.5 描述的方案,使用 eval 代码 执行定义绑定初始化步骤。
15.1.2.1.1 直接调用eval
一个 eval 函数的直接调用是表示为符合以下两个条件的 CallExpression:
解释执行 CallExpression 中的 MemberExpression 的结果是个 引用 ,这个引用拥有一个 环境记录项 作为其基值,并且这个引用的名称是 "eval"。
以这个 引用 作为参数调用 GetValue 抽象操作的结果是 15.1.2.1 定义的标准内置函数。
10.4.1.1 初始化全局执行环境
以下步骤描述 ECMA 脚本的全局执行环境 C 的创建过程:
- 将变量环境设置为 全局环境 。
- 将词法环境设置为 全局环境 。
- 将 this 绑定设置为 全局对象 。
不考虑严格模式的情况,那么简单说,就是ES5里执行eval分直接调用和非直接调用两种情况。
在直接调用的情况下,eval的执行环境就是调用它的caller的执行环境。
在非直接调用的情况下,eval的执行环境则是全局环境。
判定是否直接调用eval的依据对代码的语法解析。
比如最简单的,eval(‘this‘)就是一个CallExpression,里边eval是MemberExpression。按照直接调用的要求,MemberExpression的结果必须是个名称为eval的引用,我们这里的eval完全符合要求。所以eval(‘this‘)为直接调用。
再比如(0, eval)(‘this‘)的MemberExpression是(0, eval),JS里逗号操作符会调用内置的GetValue,所以(0, eval)的结果并不是引用,不符合直接调用的要求,所以(0, eval)(‘this‘)是一个非直接调用。
相对的,(eval)并没有逗号操作符调用GetValue,所以它的结果还是一个引用,所以(eval)(‘this‘)是直接调用。这也就解释了一开始的代码的输出结果。