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‘)是直接调用。这也就解释了一开始的代码的输出结果。

this, eval('this'), (0, eval)('this'))

时间: 2025-01-16 07:33:58

this, eval('this'), (0, eval)('this'))的相关文章

【转载】(0, eval)(‘this’)

var window = this || (0, eval)('this') 在avalon源码中有这么一行代码,var window = this很容易理解 这里复习一下Global Object: Global Object代表一个全局对象,js中不允许存在独立的函数,变量和常量,它们都是Global Object 的属性和方法,包括内置的属性和方法但是Global Object实际并不存在,它是由window充当这个角色,并且这个过程是在js首次加载时进行的 在一个页面中,首次载入js代码

(0, eval)("this")

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Document</title> <script> var name = "The window"; var object1 = { name: "My object", getNameFunc: functi

Eval与DataBinder.Eval的区别

DataBinder.Eval的基本格式 DataBinder.Eval(Container.DataItem,"XXX","{0}") <%# DataBinder.Eval(Container.DataItem,"xxxx")%>或者<%# DataBinder.Eval(Container,"DataItem.xxxx")%> 还有一种据说微软说效率很高的方式 <%# ((DataRowVi

一些数据格式化-Eval( &quot; &quot;)和DataBinder.Eval(Container.DataItem, &quot; &quot;)的区别及用法

ASP.NET 2.0改善了模板中的数据绑定操作,把v1.x中的数据绑定语法DataBinder.Eval(Container.DataItem, fieldname)简化为Eval(fieldname).Eval方法与DataBinder.Eval一样可以接受一个可选的格式化字符串参数.缩短的Eval语法与DataBinder.Eval的不同点在于,Eval会根据最近的容器对象(例如DataListItem)的DataItem属性来自动地解析字段,而DataBinder.Eval需要使用参数来

eval与window.eval的差别

eval与window.eval的差别 它们之间有区别吗? 开发过程中似乎很少有人去加个额外的window,觉得多此一举.比如Ajax过程中回调函数解析JSON格式字符串 1 ... 2 function callback(str){ 3 var json = eval('(' + str + ')'); 4 } 5 ... 但由于各个引擎实现差异,它们的区别还是有的.通常直接使用eval,而非var json = window.eval('(' + str + ')');又比如调试时使用ale

python基础&mdash;&mdash;元组、文件及其它

Python核心数据类型--元组 元组对象(tuple)是序列,它具有不可改变性,和字符串类似.从语法上讲,它们便在圆括号中,它们支持任意类型.任意嵌套及常见的序列操作. 任意对象的有序集合:与字符串和列表类似,元组是一个位置有序的对象集合(内容维持从左到右的顺序),可以嵌入到任何类别的对象中. 通过偏移存取:同字符串.列表一样,在元组中的元素通过偏移(而不是键)来访问.支持基于偏移的操作.如索引和分片. 属于不可变序列类型:类似字符串,元组是不可变的,它们不支持应用在列表中任何原处修改的操作.

盘点让人崩溃的表单样式之 (行列置换)

一般情况下使用循环显示数据都是逐行循环,这也符合数据库的查询形式,不过有些页面必须要跟实体纸质的文档格式保持一致,比如上图,要将查询到的数据逐列绑定,每页显示4条数据,我的做法是,先按传统的逐行循环绑定数据,再用CSS将table旋转90度.具体做法如下: 1.逐行循环绑定: 前端页面无论是aspx还是mvc,都是循环table 的tr,其中添加了表单元素,因为后面要说到的不只是展示数据. <table class="table-hover table-list table-list-it

编写高质量JS代码的68个有效方法(四)

[20141129]编写高质量JS代码的68个有效方法(四) *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* BLOCKS =============================================================================*/ p, blockquote, ul, ol, dl, table,

shell中的`和$()、eval、crontab

一.`和$()的区别 反引号本身就对\进行了转义,保留了本身意思,如果我们想在反引号中起到\的特殊意义,我们必须使用2个\来进行表示. 反引号中: \\ = \ $()中:\ = \ 注意:反引号是老的用法,$()是新的用法,不管是在学习测试中,还是在实际工作中,$()的用法都是被推荐的. 二.eval命令 eval会对后面的命令进行两遍扫描,如果第一遍扫描后,命令是个普通命令,则执行此命令:如果命令中含有变量的间接引用,则保证间接引用的语义. 例如有一个文件test1,里面的内容是"this