首页 > 代码库 > 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 代码 的执行环境时,执行以下步骤:

  1. 如果没有调用环境,或者 eval 代码 并非通过直接调用(15.1.2.1.1)eval 函数进行评估的,则
    1. 按(10.4.1.1)描述的初始化全局执行环境的方案,以 eval 代码 作为 C 来初始化执行环境。
  2. 否则
    1. 将 this 绑定设置为当前执行环境下的 this 绑定。
    2. 将词法环境设置为当前执行环境下的 词法环境 。
    3. 将变量环境设置为当前执行环境下的变量环境。
  3. 如果 eval 代码 是 严格模式下的代码 ,则
    1. 令 strictVarEnv 为以词法环境为参数调用 NewDeclarativeEnvironment 得到的结果。
    2. 设置词法环境为 strictVarEnv。
    3. 设置变量环境为 strictVarEnv。
  4. 按 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 的创建过程:

  1. 将变量环境设置为 全局环境 。
  2. 将词法环境设置为 全局环境 。
  3. 将 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‘)是直接调用。这也就解释了一开始的代码的输出结果。