执行上下文与执行上下文栈

191 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第21天,点击查看活动详情

执行上下文与执行上下文栈

变量提升和函数提升

Javascript代码是由浏览器中的Javascript解析器来执行的。Javascript解析器在运行Javascript代码的时候分为两步:预解析和代码执行
①预解析: JS引擎会把JS中所有的var还有function提升到当前作用域的最前面
②代码执行: 按照代码书写的顺序从上往下执行

1.变量声明提升
通过var定义(声明)的变量,在定义语句之前就可以访问到
把所有的变量声明提升到👉当前作用域👈的最前面,不提升赋值操作(所以值为undefined)

2.函数声明提升
通过function声明的函数,在之前就可以直接调用
把所有的函数声明提升到👉当前作用域👈的最前面,不调用函数

案例:

        console.log(num); //输出undefined
        var num = 10;
        // 当js引擎读到这两行代码,它先执行了预解析
        // 相当于以下代码:
        // var num;              变量提升,提升到当前作用域的最前面,但是没有提升赋值
        // console.log(num);    因为声明了变量,但们没有赋值,所以它的值才会是undefined
        // num = 10;    最后按顺序执行,执行到这里才赋值
        
        
        fn();
        function fn() {
            console.log(11);
        }
        //js引擎读到这里时,会进行预解析,相当于以下代码:
        // function fn() {      预解析会把函数声明提前,所以不用像其他语言一样担心声明和调用的顺序
        //     console.log(11);
        // }
        // fn();

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


预解析的变量提升和函数提升引申出了“执行上下文与执行上下文栈”
或者说变量提升和函数提升是执行上下文与执行上下文栈的结果

执行上下文:

在浏览器执行javascript代码之前,浏览器会做一些准备工作
从准备工作这一操作开始,直到对应的这一作用域的所有代码被执行完,这样的一个过程就叫做执行上下文;
执行上下文可以被看成一个对象,这个对象就是用来管理其对应作用域中的各个数据,这些数据就是对象中的属性。

或者可以这么理解

每次当控制器转到可执行代码的时候,就会进入一个执行上下文。执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。

JavaScript中的运行环境大概包括三种情况。

全局环境: JavaScript代码运行起来会首先进入该环境
(在执行全局代码前将window确定为全局执行上下文)
全局环境中的一些准备工作:

  • 找到标记的全局变量,并为其赋值为undefined;
  • 给this赋值为window对象
  • 函数声明,并给函数赋值为整个函数块

.

函数环境: 当函数被调用执行时,会进入当前函数中执行代码
(在调用函数,准备执行函数体之前,创建对应的函数执行上下文对象)
函数作用域中的一些准备工作

  • 找到标记的局部变量,并为其赋值为undefined;
  • 给this赋值为window对象;
  • 函数声明,并给函数赋值为整个函数块;
  • 给参数赋值; (管理参数的数组对象)arguments赋值;
  • 确定自由变量的取值作用域,并给自由变量赋值;

eval: 可忽略(我不会,哈哈)

由上面几点看出:

  1. 函数被定义的时候,就确定了函数体内部变量的作用域;
  2. 函数每被调用一次,就会产生一个新的执行上下文对象,因为不同的调用会有不同的参数;

执行上下文栈

由上述可知在一个JavaScript程序中,必定会产生多个执行上下文,JavaScript引擎会以栈的方式来处理它们,栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。

具体流程:

  1. 在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
  2. 在全局执行上下文(window)确定后,将其添加到栈中
  3. 在函数执行上下文创建后,将其添加到栈中
  4. 在当前函数执行完后,将栈顶的对象移除
  5. 当所有的代码执行完后,栈中只剩下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函数执行上下文

案例2:

        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);

上面代码依次输出
global begin: undefined
foo() begin:1
foo() begin:2
foo() begin:3
foo() end:3
foo() end:2
foo() end:1
global end:1

上面的代码画图可以很好理解

整个过程中产生了5个执行上下文:
5个 ==> window foo(1) foo(2) foo(3) foo(4) 【window是栈底】


emmm。这个方面的知识我是抽象理解的,这些总结也是我把四面八方的知识缝合起来的。。。所以如果有错误的话,希望给为爷指点指点,谢谢!