You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
在全局执行上下文中,this 的值指向全局对象(在浏览器中,this引用 Window 对象)。
如果是在函数执行上下文中,this的值取决于该函数是如何被调用的。如果它被一个引用对象调用,那么 this 会被设置成那个对象,否则 this 的值被设置为全局对象或者 undefined(在严格模式下),除此之外,我们还可以使用call、apply和bind指定this。
LHS 和 RHS
在说明执行上下文和作用域之前,我们需要了解一下LHS和RHS查询,这样才能更好的理解为什么JavaScript存在执行上下文和作用域。
我们都知道JavaScript是一门解释型语言/动态/脚本语言,他会在执行之前一刻进行编译,然后交给JS引擎执行。
编译
编译器负责把代码解析成机器指令,通常会有三个步骤:
词法单元(token)
,如var a = 2
=>var
、a
、=
和2
。token
的流(数组)转为抽象语法树(AST)
AST
转为机器指令,等待执行。执行
JS引擎拿到一堆机器指令开始执行,这个时候需要查询变量,LHS和RHS就登场了。
a=1
,是为了将值1
赋给变量a
。foo()
,查找foo
是函数,才能执行;如果不是函数就会抛出TypeError
异常;找不到则会抛出ReferenceError
异常。而两种查询方法获取变量的地方,就叫做执行上下文(也叫作用域)。
什么是执行上下文
执行上下文,其包含定义变量的词法环境(Lexical Environment)和上下文(this),同时也控制着代码对变量的访问规则。
所有的 JavaScript 代码在运行时都是在执行上下文中进行的,每创建一个执行上下文,就会将当前执行上下文放到一个栈顶,这就就是我们常说的执行栈。
执行上下文的创建
何时创建执行上下文
JavaScript 中有三种情形会创建新的执行上下文:
创建执行上下文具体分析
执行上下文的创建大体步骤如下:
在全局执行上下文中,
this
的值指向全局对象(在浏览器中,this
引用Window
对象)。如果是在函数执行上下文中,
this
的值取决于该函数是如何被调用的。如果它被一个引用对象调用,那么this
会被设置成那个对象,否则this
的值被设置为全局对象或者undefined
(在严格模式下),除此之外,我们还可以使用call
、apply
和bind
指定this
。语法环境是基于 ECMAScript 代码的词法嵌套结构,来定义标识符与特定变量和函数的关联关系,由
环境记录(Environment Record)
和可能为空引用(null
)的外部词法环境
组成,用于let
、const
的声明。变量环境继承于词法环境,区别在于其用于var
声明,因为其定义的变量可以提升。(ECMA成员说明:About "LexicalEnvironment" and "VariableEnvironment" )这一步会创建变量及其关系,在全局执行上下文中(这里对两种环境不做区分),会:
非函数中的var声明
、顶级函数声明
、顶级let const class声明
和块级作用域声明的变量和函数
var声明
并初始化为undefined
(同时会绑定到this
),登记顶级函数
并初始化并赋值,登记let const class声明
但未初始化(这里也就是我们常说的变量提升)。块级作用域内部的变量和函数比较特殊,对于变量中var
变量和函数会提升(如果顶级存在同名的let cosnt class 声明
则不会提升),而且二者可以在这部分代码运行后被使用。其他的声明方式不会提升。null
。在函数上下文中也类似:
var声明
、函数声明
、let const class声明
和块级作用域声明的变量和函数
伪代码如下:
作用域和执行上下文的关系
在MDN中,可以发现,二者其实是一个含义,只不过称呼不同,之前我也困惑了许久,下面也将使用作用域去代指执行上下文,如果还有疑问,可以在浏览器JavaScript代码执行中打个断点,在开发者工具中右侧区域可以找到

scope
这一栏,也侧面验证了这一点。所以:
this
,可以认为它只存在语法环境,保存这标识及其引用关系。作用域链
当访问一个变量时,解释器会首先在当前作用域查找标示符,如果找到了,则使用当前作用域下的变量,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不在父作用域中,这就是作用域链。
那这个父作用域又是那个呢?实际上是要到创建这个函数的那个域。 作用域中取值,这里强调的是“创建”,而不是“调用”,切记切记——这种类型的作用域又称为静态作用域,也被称为词法作用域,因为在词法分析时就确定了查找关系。
上面代码会打印
1
,为什么呢?因为此处foo
的函数定义是在全局作用域window
上,所以查找时现在foo
函数中查找a
,找不到会去window
上查找,所以此处a=1
。顺便在看下
this
的,感受下其中的不同,当然不想看的可以跳过此处的两个输出会打印
1
,第一个大家可能容易理解,为什么第二个也是1
呢?此处foo
被调用时,其执行上下文指向依然是全局上下文,所以这里的this
也指向window
,所以此处a=1
。变量提升
上面说过,JavaScript中,存在函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部,这就是我们常说的变量提升,注意:
let
和const
也存在变量提升,但是let
和const
定义的变量会造成暂时性死区,定义变量后一开始就形成了封闭作用域,凡是在声明之前就使用这些变量,就会报错。var
和let
的声明提升:当前作用域下只要存在变量,就算是变量提升得到,也相当于找到了,不会再去父作用域中找:
函数定义也存在变量提升,而且是整体提升,如果是函数变量则看定义的关键字是
var
还是其他,这和上文保持一致:The text was updated successfully, but these errors were encountered: