调用堆栈(1)--Js中执行栈和执行上下文

执行栈: 也叫调用栈,用于储存代码执行期间的所有执行上下文,具有先进后出(LIFO)的结构,用于管理维护执行上下文

执行上下文: 是当前js代码被解析和执行时所在环境的抽象概念,js中代码都在上下文中执行

执行上下文

  • 全局执行上下文: 只有一个,js执行时一直在执行栈底,直到执行结束,才出栈
  • 函数执行上下文: 若干个,函数调用时才创建,函数执行结束出栈
  • Eval函数执行上下文: 指运行在eval函数中的代码,很少用

全局执行上下文会一直在栈底吗?

1
2
3
全局执行上下文不会一直在栈底,也就是在关闭页面才出栈是不对的.
全局代码执行结束,全局上下文出栈,但词法环境还在,如在控制台执行代码,会重新根据已有全局词法环境创建全局上下文,因此能正常访问全局变量等.
执行异步任务队列任务时也是如此,当执行栈为空时,会从任务队列中提取结果,创建上下文,执行入栈出栈操作.

执行上下文的创建

分两个阶段: 创建,执行阶段

创建阶段

  • 1.确定this绑定(this binding)
  • 2.词法环境组件创建(LexicalEnvironment)
  • 3.变量环境组件创建(VariableEnvironment)

    执行阶段的词法环境和变量环境,都指向最初的词法环境对象,其中包含环境记录项和外部环境引用.
    this绑定不属于词法环境,是执行环境中,执行上下文创建确定的.

注: 词法环境(词法环境对象),是一个用于定义特定变量和函数标识符在 ECMAScript 代码的词法嵌套结构上关联关系的规范类型。一个词法环境由一个环境记录项和可能为空的外部词法环境引用构成。参考1,参考2

ES6之前有VO(变量对象),AO(活动对象),scope(作用域链),this binding的说法.
VO,AO相当于词法环境和变量环境中的环境记录,函数声明变量声明阶段
this binding即确定this绑定
scope相当于可能为空的外部词法环境引用,实现作用域链

VO,AO,scope参考
VO,AO,GO详解

1
2
3
4
5
ExecutionContext = {  // 执行上下文
ThisBinding = <this value>, // 确定this
LexicalEnvironment = { ... }, // 词法环境
VariableEnvironment = { ... }, // 变量环境
}

this binding

  • 全局执行上下文中,this指向全局对象(window)
  • 函数执行上下文中,this指向取决于函数的调用形式(详见【this全面解析】)

词法环境(LexicalEnvironment)

  • 环境记录: 储存变量和函数声明的实际位置
  • 对外部环境的引用: 可以访问其外部词法环境

在全局和函数上下文中的表现:

  • 全局: 外部环境引用为null,环境记录中拥有一个全局对象(window)及其关联的属性方法(如数组方法等)以及用户自定义的所有全局变量,this指向全局对象
  • 函数: 外部引用环境可能是全局或外部函数,函数中的变量储存在环境记录中,包含arguments对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    GlobalExectionContext = {  // 全局执行上下文
    LexicalEnvironment: { // 词法环境
    EnvironmentRecord: { // 环境记录
    Type: "Object", // 全局环境
    // 标识符绑定在这里
    }
    outer: <null> // 对外部环境的引用
    }
    }

    FunctionExectionContext = { // 函数执行上下文
    LexicalEnvironment: { // 词法环境
    EnvironmentRecord: { // 环境记录
    Type: "Declarative", // 函数环境
    // 标识符绑定在这里
    }
    outer: <Global or outer function environment reference> // 对外部环境的引用
    }
    }

变量环境(VariableEnvironment)

也是一种词法环境,因此有同词法环境的属性

  • 区别: ES6中,词法环境用于储存函数声明和变量(let,const)绑定;变量环境储存变量(var)绑定

例:

1
2
3
4
5
6
7
8
9
10
let a = 20;  
const b = 30;
var c;

function multiply(e, f) {
var g = 20;
return e * f * g;
}

c = multiply(20, 30);

执行上下文如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
GlobalExectionContext = {   // 全局执行上下文

ThisBinding: <Global Object>,

LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录
Type: "Object",
// 标识符绑定在这里 变量声明(let,const在这)
a: < uninitialized >, // 主要变量未初始化
b: < uninitialized >,
multiply: < func > // 函数声明也在这
}
outer: <null>
},

VariableEnvironment: { // 变量环境
EnvironmentRecord: { // 环境记录
Type: "Object",
// 标识符绑定在这里 变量声明(var在这)
c: undefined, // 注意值是undefined
}
outer: <null>
}
}

FunctionExectionContext = { // 函数执行上下文

ThisBinding: <Global Object>,

LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录
Type: "Declarative",
// 标识符绑定在这里 函数arguments对象在词法环境
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>
},

VariableEnvironment: { // 变量环境
EnvironmentRecord: { // 环境记录
Type: "Declarative",
// 标识符绑定在这里
g: undefined // 注意值是undefined
},
outer: <GlobalLexicalEnvironment>
}
}

变量提升

执行上下文创建阶段,函数声明在词法环境中已经完成,所以到执行阶段即可实现提升效果;
执行阶段,var声明会赋值undefined,而let,const不会赋值,是未初始化状态(暂时性死域),所以var会提升,let会报错
函数声明提升优先于变量提升

1
2
3
4
5
6
7
8
alert(a); //输出:fn
function a() {
alert("我是函数");
}
var a; //hoisting
alert(a); //输出:fn;此处var a的声明被函数声明覆盖掉,所有不赋值时不会改变a
a = "我是变量"; //赋值
alert(a); //输出:'我是变量'

执行阶段

所有变量分配已完成,执行代码,如果之前声明的let没有值,会再次赋值undefined