什么是执行上下文
所有的执行上下文都是存储对象,这个存储对象已经绑定好了this,处理好了对象名(或函数名)与对象(或函数)的映射关系。
一个执行上下文是由变量对象、作用域链、this指针
执行上下文有三种类型
- 全局执行上下文: 也可以称之为默认上下文或基础上下文,任何不在函数内部的代码都存储在全局上下文中。除此之外,全局执行上下文还要执行两件事:1. 创建一个全局的
window对象(浏览器的情况下);2. 设置this的值等于这个全局对象。一个程序只会有一个全局执行上下文。 - 函数执行上下文 — 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(将在后文讨论)执行一系列步骤。
- Eval 函数执行上下文 — 执行在
eval函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用eval,所以在这里我不会讨论它。
执行上下文的生命周期
一个执行上下文的生命周期可以分为3个阶段
- 创建阶段:这个阶段会创建变量对象,建立作用域链,确定
this指向 - 执行阶段:会在这个阶段完成变量的赋值,函数的引用以及其他代码的执行。
- 最后执行完毕,出栈等待回收。
执行栈
执行栈是用来存储代码运行时创建的所有执行上下文。
当JavaScript引擎第一次遇到脚本时,它会创建一个全局执行上下文并且亚茹当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。
引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。
怎么创建执行上下文
创建分两个阶段
- 创建阶段
- 执行阶段
1. 创建阶段
在 JavaScript 代码执行前,执行上下文将经历创建阶段。在创建阶段会发生三件事:
this值的决定,即我们所熟知的 This 绑定。- 创建词法环境组件。
- 创建变量环境组件。
1.1 This绑定
在全局执行上下文中,this 的值指向全局对象。(在浏览器中,this引用 Window 对象)。
在函数执行上下文中,this 的值取决于该函数是如何被调用的。如果它被一个引用对象调用,那么 this 会被设置成那个对象,否则 this 的值被设置为全局对象或者 undefined(在严格模式下)。例如:
let foo = {
baz: function() {
console.log(this);
}
}
foo.baz(); // 'this' 引用 'foo', 因为 'baz' 被
// 对象 'foo' 调用
let bar = foo.baz;
bar(); // 'this' 指向全局 window 对象,因为
// 没有指定引用对象
1.2 词法环境
词法环境是变量名(可以是变量名也可以是函数名)和变量(可以是变量也可以是函数)的一种映射结构。
词法环境有两个组件,分别是环境记录器和外部环境的引用
- 环境记录器是存储变量和函数声明的实际位置。
- 外部环境的引用意味着它可以访问其父级词法环境(作用域)。
**词法环境有两种类型: ** - 全局环境: 没有外部环境的引用。它拥有内建的
Object/Array/等、在环境记录器内的原型函数(关联全局对象,比如 window 对象)还有任何用户定义的全局变量,并且this的值指向全局对象。 - 在函数环境中,函数内部用户定义的变量存储在环境记录器中。并且引用的外部环境可能是全局环境,或者任何包含此内部函数的外部函数。
环境记录器也有两种类型(如上!):
- 声明式环境记录器存储变量、函数和参数。(在函数环境中,注意在此环境中还包含了一个传递给函数的
arguments对象和传递给函数的参数的length)。 - 对象环境记录器用来定义出现在全局上下文中的变量和函数的关系。(在全局环境中)
1.3 变量环境
变量环境也是一个词法环境,它有着上面定义的词法环境的所有属性。但在ES6中,词法环境组件和变量环境组件的一个不同就是前者被用来存储函数声明和变量(let声明和const声明)的绑定,而后者只用来存储var变量的绑定。
2. 执行阶段
完成对所有这些变量的分配后执行代码。
注意: 如果在实际位置中找不到let变量变量声明的值,它会被赋值为undefined。