目录

🍎 JavaScript 执行上下文与执行栈

# 变量提升与函数提升

  • 变量声明提升
    • 通过 var 定义(声明)的变量,在定义语句之前就可以访问。值为 undefined
  • 函数声明提升
    • 通过 function 声明的函数,在之前就可以直接调用,值为函数定义(对象)。但是通过 var 定义的函数不适用函数提升(为变量提升)。

🌰 例子:

var a = 3
function fn(){
  // var a
  console.log(a) // undefined
  var a = 4
}
fn()
1
2
3
4
5
6
7
fn2() // undefined 可调用

function fn2(){
  console.log('fn2()')
}
1
2
3
4
5
fn3() // error

var fn3() = function (){
  console.log('fn3()')
}
1
2
3
4
5

❓ 变量提升和函数提升是如何产生的?

# 执行上下文

执行上下文是一种对 JavaScript 代码执行环境的抽象概念。也就是说,只要有 JavaScript 代码执行,就一定是运行在执行上下文中。

  • 按照位置给代码进行分类:

    • 全局代码

    • 函数代码

    • Eval 函数执行上下文。运行在 eval 函数中。很少使用而且不建议使用。

  • 全局执行上下文:(只有一个)

    • 在执行全局代码前window 确定为全局执行上下文

    • 对全局数据进行预处理

      • 使用 var 定义的全局变量值为 undefined ,添加为 window属性

      • 使用 function 声明的全局函数赋值,添加为 window方法

      • this 赋值为 window

        (在浏览器中 window 为全局对象)

    • 开始执行全局代码。

  • 函数执行上下文 :(存在无数个)

    • 调用函数时,准备执行函数体之前创建对应的函数执行上下文对象(虚拟的,存在于栈中);
    • 对局部数据进行预处理
      • 形参变量赋值 ** 实参 的数据 **,添加为执行上下文的属性;
      • arguments 赋值 ** 实参 的数据列表(伪数组)**,添加为执行上下文的属性;
      • 使用 var 定义的局部变量赋值 undefined ,添加为执行上下文的属性;
      • 使用 function 声明的函数,赋值 fun ,添加为执行上下文的方法;
      • this 赋值 调用函数的对象
    • 开始执行函数体代码。

🌰 例子:全局执行上下文

console.log(a1) // window.a1
a2() // window.a2()
console.log(this) // window

var a1 = 1
function a2() {
  console.log('a2()')
}
1
2
3
4
5
6
7
8

🌰 例子:函数执行上下文

function fn(a1) {
	console.log(a1) // 2
  console.log(a2) // undefined
  a3() // a3()
  console.log(this) // window 调用fn()的对象 
  console.log(arguments) // 伪数组(2,3)
  
  var a2 = 3
  function a3() {
    console.log('a3()')
  }
}

fn(2, 3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 生命周期

执行上下文的生命周期分为三个阶段:

  • 创建阶段;
  • 执行阶段;
  • 回收阶段;

# 创建阶段

  • 当函数被调用,但未执行任何其内部代码之前。

  • 完成三件事:

    • 确定 this 的值( this binding , this 的绑定);因为 this 的值是在执行的时候被确认的,定义的时候不能确认。
    • 创建词法环境(Lexical Environment)
    • 创建变量环境(Variable Environment)
    ExecutionContext = {
      ThisBinding = <this value>,
      LexicalEnvironment = { ... },
    	VariableEnvironment = { ... }
    }
    
    1
    2
    3
    4
    5
    • 词法环境有两个组成部分:

      • 全局环境:没有外部环境的词法环境,其外部环境引用的是 null ;有一个全局对象, this 指向这个全局对象。
      • 函数环境:用户在函数中定义的变量被存储在环境记录中,包含 arguments 对象,外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境。
    • 变量环境:也是一个词法环境,因此具有上面定义的词法环境的所有属性。

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

    🌰 例子:

    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

    letconst 定义的变量 ab 在创建阶段没有被赋值,但是 var 声明的变量从在创建阶段被赋值为 undefined 。因为创建阶段会在代码中扫描变量和函数声明, 将函数声明存储在环境中;而变量会变初始化为 undefinedvar 声明的变量)和 uninitialized (未初始化)( letconst 声明的变量)。

    这是变量提升的实际原因。

# 执行阶段

在这个阶段,执行变量赋值、代码执行。如果 JavaScript 引擎在源代码中声明的实际位置找不到变量的值,那么将为其分配为 undefined 值。

# 回收阶段

执行上下文出栈,等待虚拟机回收执行上下文。

# 执行上下文栈

  • 全局代码执行之前,JavaScript 引擎就会创建一个栈来存储管理所有的执行上下文对象
  • 在全局执行上下文 window 确定后,将其添加到栈中(压栈)。
  • (调用函数时)在函数执行上下文创建后,将其添加到栈中(压栈)。
  • 在当前函数执行完后,将栈顶的对象移除(出栈)。
  • 当所有的代码执行完后,栈中只剩下 window

🌰 例子:

// 1.进入全局执行上下文
var a =10

var bar = function(x) {
  var b = 5
  foo(x + b) // 3. 进入foo执行上下文
}

var foo = function(y) {
  var c = 5
  console.log(a + c + y)
}

bar(10) // 2. 进入bar函数执行上下文
1
2
3
4
5
6
7
8
9
10
11
12
13
14
image-20220319112348783

# 面试题

1️⃣ :

  • 依次输出的内容。
  • 整个过程产生了几个执行上下文?
console.log('global begin:' + i)
var i = 1
foo(1)
function foo(i){
  if(i == 4) {
    return;
  }
  console.log('foo() begin:' + i)
  foo(i + 1) 
  console.log('foo() end:' + i)
}
console.log('global end:' + i)
1
2
3
4
5
6
7
8
9
10
11
12
  • image-20220319131349041
  • 5

2️⃣:

function a() { }
var a
console.log(typeof a) // function
1
2
3

先执行变量提升再执行函数提升

if(!(b in window)) {
  var b = 1
}
console.log(b) // undefined
1
2
3
4
var c = 1
function c(c){
  console.log(c)
}
c(2) // TypeError: c is not a function. (In 'c(2)', 'c' is 1) 
1
2
3
4
5
📢 上次更新: 2022/09/02, 10:18:16