理解执行上下文和执行栈,有助于理解提升机制、作用域、闭包等。
目录
1.什么是执行上下文
2.执行上下文的三种类别
3.什么是执行栈(调用栈)
4.执行上下文的生命周期
4.1 创建阶段
4.1.1 确定 this 的值, 绑定 this (This Binding)
4.1.2 创建 词法环境(LexicalEnvironment)
4.1.3 创建 变量环境(VariableEnvironment)
4.1.4 为什么会发生变量提升?
4.2 执行阶段
4.3 销毁阶段
5.参考文章
举个栗子~:
执行上下文 —— JavaScript 代码被解析和执行时,所在的环境;
概念分析~:
当代码运行时,会产生一个执行环境,在这个环境中:
综上叫做 JavaScript 执行上下文;
全局执行上下文(只有一个):不在任何函数中的代码,都位于全局执行上下文中;它创建了一个全局对象,也就是浏览器对象(即 window 对象),this 指向这个全局对象;
函数执行上下文(可以有多个):在函数被调用时,才会被创建;每次调用函数时,都会创建一个新的执行上下文;
eval() 函数执行上下文:JavaScript 的 eval() 函数,执行其内部的代码时,会创建属于自己的执行上下文,(很少用而且不建议使用);
由概念和类别可以得出,执行上下文有以下特点:
JavaScript 是单线程的,只能同时做一件事;当多个执行上下文存在时,如何控制它们的执行顺序呢?
执行栈就是负责管理多个执行上下文的,它存储了代码执行期间所有的执行上下文;
举个栗子~:
这就是一个压栈出栈(后进先出 / LIFO last-in, first-out)的过程,如下图所示:
综上所述,处于 活动状态 的执行上下文环境,始终只有一个:
再举个相对复杂的栗子~
var a = 'Hello World!'; // 1.进入全局上下文执行环境
function first() {
console.log('Inside first function');
second(); // 3.进入 second 函数上下文执行环境;second 函数执行完成后,返回 first 函数上下文执行环境
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first(); // 2.进入 first 函数上下文执行环境
console.log('Inside Global Execution Context'); // 4.first 函数执行完成后,返回 全局上下文执行环境;如果没有其他代码需要执行,则执行栈会把 全局执行上下文 从执行栈中弹出 pop
// 最终打印结果:
// Inside first function
// Inside second function
// Again inside first function
// Inside Global Execution Context
1)创建阶段 —— 确定 this 指向、生成变量对象、建立作用域链
2)执行阶段 —— 变量赋值、函数引用、执行其他代码
3)销毁阶段 —— 执行完毕出栈,等待回收被销毁
刚看上面的三个阶段,你肯定是一脸懵逼的,我们挨个分析
创建阶段的工作分为:
this 在不同的执行上下文中,是指向不同内容的:
函数执行上下文的 this 指向,可以参考下面的文章:
JavaScript深入之史上最全--5种this绑定全面解析 | 木易杨前端进阶高级前端进阶之路https://muyiy.cn/blog/3/3.1.html
简单概括 函数执行上下文 中的 this 绑定:
let person = {
name: 'peter',
birthYear: 1994,
calcAge: function() {
console.log(2018 - this.birthYear);
}
}
person.calcAge();
// 'this' 指向 'person', 因为 'calcAge' 是被 'person' 对象引用调用的。
let calculateAge = person.calcAge;
calculateAge();
// 'this' 指向全局 window 对象,因为没有给出任何对象引用
词法环境是一种规范类型,基于 ECMAScript 代码的词法嵌套结构来定义标识符与特定变量和函数的关联关系。词法环境由环境记录(environment record)和可能为空引用(null)的外部词法环境组成。
简而言之,词法环境是一个包含标识符变量映射的结构。(这里的标识符表示变量/函数的名称,变量是对实际对象【包括函数类型对象】或原始值的引用)
词法环境包含两个部分:
不同执行上下文的词法环境不同(以 全局、函数 为例)
全局执行上下文中:
GlobalExectionContext = { // 全局执行上下文
LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录
Type: "Object", // 全局环境
// 对外部环境的引用
outer:
}
}
函数执行上下文中:
FunctionExectionContext = { // 函数执行上下文
LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录
Type: "Declarative", // 函数环境
// 对外部环境的引用
outer:
}
}
对于 函数执行上下文 而言,环境记录 还包含了一个 arguments
对象,该对象包含了索引和传递给函数的参数之间的映射以及传递给函数的参数的 长度(数量)
function foo(a, b) {
var c = a + b;
}
foo(2, 3);
// arguments 对象
Arguments: {0: 2, 1: 3, length: 2},
变量环境的本质,也是一个词法环境,它具有上面定义的 词法环境 的所有属性
在 ES6 中,词法环境 和 变量环境有以下区别:
举个栗子~:
var a;
var b = 1;
let c = 2;
const d = 3;
function fn (e, f) {
var g = 4;
return e + f + g;
}
a = fn(10, 20); // 函数被调用了,才会创建 函数执行上下文
把上方代码的执行上下文拆解一下,全局执行上下文包括:
GlobalExectionContext = { // 全局执行上下文
ThisBinding: ,
LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录
Type: "Object", // 全局环境
c: < uninitialized >,
d: < uninitialized >,
fn: < func >
},
outer: // 外部环境引用
},
VariableEnvironment: { // 变量环境
EnvironmentRecord: { // 环境记录
Type: "Object",
a: < undefined >,
b: < undefined >
},
outer:
}
}
把上方代码的执行上下文拆解一下,函数执行上下文包括:
FunctionExectionContext = {
ThisBinding: ,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
Arguments: {0: 20, 1: 30, length: 2},
},
outer:
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
g: undefined
},
outer:
}
}
在 执行上下文环境 的创建阶段:
因此,可以在变量声明之前:
举个栗子~
function fn(){
console.log(a); // undefined;
var a = 1;
}
fn();
相当于
function fn(){
var a;
console.log(a);
a = 1;
}
fn();
执行阶段主要做这些事情:
如果 Javascript 引擎在 变量声明 的实际位置,找不到 let 变量的值,那么将为 let 变量分配 undefined 值
执行完毕出栈,等待回收被销毁
【译】理解 Javascript 执行上下文和执行栈 - 掘金如果你是一名 JavaScript 开发者,或者想要成为一名 JavaScript 开发者,那么你必须知道 JavaScript 程序内部的执行机制。理解执行上下文和执行栈同样有助于理解其他的 JavaScript 概念如提升机制、作用域和闭包等。 正确理解执行上下文和执行栈的…https://juejin.cn/post/6844903704466833421