JS执行上下文与调用栈(进阶必备知识)

446 阅读4分钟

这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战

前言

TIP 👉 JS执行上下文及相关调用栈层面的只是,已经是JS语言类考题里面非常身图和底层的考察点。

执行上下文是什么

执行上下文,从定义上理解,是“执行代码的环境”

执行上下文的分类

  • 全局上下文 全局代码所处的环境,不再函数中国年的代码都在全局执行上下文中
  • 函数上下文 在函数调用时创建的上下文
  • Eval执行上下文 运行Eval函数中的代码时所创建的环境,eval被诟病多年,不讨论

1. 全局上下文的创建和组成

当我们的JS脚本跑起来之后,第一个被创建的执行上下文就是全局上下文。

当我们的脚本里一行代码页没有的时候,全局上下文只有两个东西:

  • 全局对象(浏览器是window,Node环境是Global)
  • this变量,这里的this,指向的还是全局
var name = ‘qinglian’
var tel = '18519999999'
function getName() {
    return {
        name: name
    }
}

创建阶段全局上下文

image.png

2. 上下文生命周期

  • 创建阶段 ----- 执行上下文的初始化状态,此时一行代码都没有执行
  • 执行阶段 ----- 逐行之行脚本里的代码

创建阶段里

  • 创建全局对象
  • 创建this,指向全局
  • 给变量和函数安排内存空间
  • 默认给变量赋值undefined,将函数声明放入内存
  • 创建作用域链

执行阶段该有值的都有值了,这是因为JS引擎已经在一行一行之行代码、之行赋值操作了。

执行上下文在执行阶段里歧视始终是处在一个动态,比如你执行完第一行没执行第二行的时候,这时候就只有name有值了,而tel还是undefined,再往下执行,tel就有值了

站在执行上下文的角度,理解“变量提升”的本质

其实根本不存在任何的“提升”,变量一直在原地,所谓的“提升”,只是变量的创建过程(上下文呢创建阶段)和赋值过程(上下文执行阶段)的不同步带来的一种错觉。

执行上下文在不同阶段完成的不同工作,才是“变量提升”的本质

函数上下文的创建和组成

函数上下文不会创建全局对象,而是创建参数对象(arguments):创建出的this不再死死指向全局对象,而是取决于该函数是如何被调用的----------如果它被一个引用对象调用,那么this就指向这个对象;否则,this的值会被设置为全局对象活着undefined

var name = ‘qinglian’
var tel = '18999999999'
function getNameTel() {
    retunr {
        name: name,
        tel: tel
    }
}

getNameTel()

当引擎执行到getNameTel调用这一行时,首先会进入函数上下文的创建阶段,

image.png

接着进入执行阶段,逐行执行函数内部的代码,此处我们只有一行代码,在代码执行过程中,没有涉及到变量的修改,因此函数上下文的内容保持不变,执行完毕后,函数上下文的生命周期就结束了。

调用栈

当函数执行完毕后,其对应的执行上下文也随之消失了,这个消失的过程,我们叫它“出栈”,在JS代码的执行过程中,引擎会为我们创建“执行上下文栈”也就是调用栈

function testA() {
    console.log('执行第一个测试函数的逻辑')
    testB();
    console.log('再次执行第一个测试函数的逻辑')
}

function testB() { 
    console.log('执行第二个测试函数的逻辑')
}

testA()

执行之初,全局上下文创建:

image.png

执行到testA调用处,testA对应的函数上下文创建:

image.png

执行到testB调用处,testB对应的函数上下文创建:

image.png

testB执行完毕,对应上下文出栈,剩下testA和全局上下文:

image.png

testA执行完毕,对应执行上下文出栈,剩下全局上下文:

image.png

整个过程的调用栈:

image.png

站在调用栈的角度,理解作用域

作用域其实就是当前所处的执行上下文。

注意:沿着作用域链找,可不是沿着调用栈一层一层往上找,调用栈实在执行过程中形成的,而作用域链可是在书写阶段就决定的了