这是我参与更文挑战的第2天,活动详情查看: 更文挑战
执行上下文 执行环境 (execution context)
当 JavaScript 代码执行一段可执行代码(executable code, EC
)时,会创建对应的执行上下文
有三种执行上下文:
- 全局执行上下文:这是默认或者说基础的上下文,任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的
window
对象(浏览器的情况下),并且设置this
的值等于这个全局对象。一个程序中只会有一个全局执行上下文 - 函数执行上下文:每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序执行一系列步骤
eval
函数执行上下文:执行在eval
函数内部的代码也会有它属于自己的执行上下文
通过创建了执行上下文栈(Execution context stack,ECS
)来管理执行上下文
执行上下文有三个重要属性:
- 变量环境(
Variable object,VO
) - 作用域链(
Scope chain
) this
执行上下文会分成两个阶段来处理:
- 创建
this
值的决定- 创建词法环境
- 创建变量环境
- 执行
this
this
所指向的对象:
- 全局作用域中,上下文总是
window
对象 - 如果作用域定义在一个对象的方法中,上下文就是这个方法所在的那个对象
- 如果使用
new
调用函数时,上下文会被设置为调用函数的实例
在使用严格模式下的全局作用域中调用函数时,上下文默认是 undefined
,因为严格模式禁止 this
指向全局对象。
词法环境
词法环境是一种持有标识符—变量映射的结构。(这里的标识符指的是变量/函数的名字,而变量是对实际对象[包含函数类型对象]或原始数据的引用)。
词法环境的内部有两个组件:
- 环境记录器:环境记录器是存储变量和函数声明的实际位置
- 外部环境的引用:外部环境的引用意味着它可以访问其父级词法环境(作用域)
词法环境有两种类型:
- 全局环境(在全局执行上下文中)是没有外部环境引用的词法环境。全局环境的外部环境引用是
null
。它拥有内建的 Object/Array/等、在环境记录器内的原型函数(关联全局对象,比如window
对象)还有任何用户定义的全局变量,并且this
的值指向全局对象。 - 在函数环境中,函数内部用户定义的变量存储在环境记录器中。并且引用的外部环境可能是全局环境,或者任何包含此内部函数的外部函数。
环境记录器也有两种类型:
- 声明式环境记录器存储变量、函数和参数。
- 对象环境记录器用来定义出现在全局上下文中的变量和函数的关系。
简而言之:
- 在全局环境中,环境记录器是对象环境记录器。
- 在函数环境中,环境记录器是声明式环境记录器。
注意 — 对于函数环境,声明式环境记录器还包含了一个传递给函数的 arguments
对象(此对象存储索引和参数的映射)和传递给函数的参数的 length
。
变量环境
它同样是一个词法环境,其环境记录器持有变量声明语句在执行上下文中创建的绑定关系。
如上所述,变量环境也是一个词法环境,所以它有着上面定义的词法环境的所有属性。
在 ES6
中,词法环境组件和变量环境的一个不同就是前者被用来存储函数声明和变量(let 和 const
)绑定,而后者只用来存储 var
变量绑定。
全局作用域和全局变量
如果变量定义在函数之外,则变量就处于全局作用域中:
// 默认全局作用域
var a = 1;
function fn() {}
复制代码
全局作用域中的变量可以在任何其他作用域中访问和修改:
var a = 1;
function fn() {
console.log(a); // 显示 a
a = 2; // 修改 a
}
复制代码
全局变量的声明有三种方式:
- 不带
var
无论是在哪里,甚至是在函数内部声明,都会隐式声明成全局变量 - 带
var
在函数外部声明,这种是显式声明 - 使用
window
全局对象来声明,全局对象的属性对应也是全局变量
全局变量的优点:
可以减少变量个数,减少由于实参和形参的数据传递带来的时间消耗
全局变量的缺点:
- 全局变量保存在静态存储区,程序开始运行时为其分配内存,程序结束释放内存,和局部变量的动态分配、动态释放相比,生存期比较长,过多的全局变量会占用较多的内存单元
- 全局变量破坏了函数的封装性能,使得函数对全局变量产生依赖,同时降低了函数的可移植性
- 全局变量使得函数的可读性降低,由于多个函数可能使用相同的全局变量,对于程序的查错和调试都非常不利
注:尽可能少的使用全局变量
局部作用域
javascript
和 C++
之类的语言不同,它的局部作用域是以函数为单位,而不是块级作用域。(注:最新的标准提供了块级作用域)
每个函数被调用的时候都具有不同的作用域,意味着相同名称的变量可以在不同的函数中使用,这是因为它们都是属于不同的作用域,在其他函数中不可被访问。
块级作用域
块级声明包括:
if
switch
for
while
es6
中引入了 let
和 const
,这些关键字支持在块级声明中创建使用局部作用域。
生存周期
全局作用域的生存周期和应用相同,局部作用域只在函数调用执行期间存在。
作用域链
当查找变量的时候,会先从当前上下文的变量环境中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量环境中查找,一直找到全局上下文的变量环境,也就是全局对象。这样由多个执行上下文的变量环境构成的链表就叫做作用域链。
在函数定义的时候创建(作用域在定义的时候决定所以作用域链也是)
作用域链包含变量环境,作用域链用于解析变量,当解析一个变量时,开始从最内层沿着父级寻找所需的变量或者其他资源,作用域链包含自己执行环境以及父级环境中包含的变量环境
内层函数(必须是函数声明)可以访问父级作用域的变量等资源。意味着子函数词法绑定到了父级执行环境。
- 子执行环境可以访问父级变量,反之不行
- 在不同执行环境中同名变量优先级在执行