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

1. 变量提升与函数提升

①  变量声明提升:通过var 定义(声明)的变量,在定义语句之前就可以访问到,值为undefined;
②  函数声明提升:通过function声明的函数,在之前就可以直接调用,值:函数定义(对象)

1.1 代码体验

<script>
    /* 经典面试题 */
    var a = 3;
    function fn() {
        console.log(a);
        var a = 4
    };
    fn();
    // 问:输出结果是什么?
    // 答:输出 undefined
    /* 分析:上述代码实际运行顺序如下: */
    var a;
    a = 3;
    function fn() {
        var a;
        console.log(a);  // 此时根据就近原则输出a的值,而a只声明未赋值,所以为undefined
        a = 4;
    };
    fn();

    /* 体验变量声明提升和函数声明提升的作用 */
    console.log(b);   // undefined,未定义前可访问到,变量提升
    fn2() // 可调用, 存在函数提升
    fn3()  // 不可调用,只是存在变量提升,没有函数提升
    var b=3;
    function fn2(){
        console.log('fn2()');
    };
    var fn3=function(){
        console.log('fn3()');
    };
</script>

注意只有通过function声明的函数才具有提升功能,通过赋值操作设置的函数表达式并不具有,只能在声明函数后,才可进行调用,如上述代码中的函数fn3,不可在前调用。

那么问题来了:变量提升和函数提升是如何产生的呢?这就涉及到执行上下文的相关知识了...

2. 执行上下文

JS代码根据位置可以分为:全局代码 和 函数(局部)代码。从而执行上下文也就分为全局执行上下文和函数执行上下文。

2.1 全局执行上下文

① 在执行全局代码前,将window确定为全局执行上下文
② 对全局数据进行预处理(预解析):

  1)var定义的全局变量==> undefined,添加为window的属性
  2)function声明的全局函数==>赋值(fun),添加为window的方法
  3)this==>赋值(window)

③ 开始执行全局代码

2.2 函数执行上下文

① 在调用函数,准备执行函数体之前创建对应的函数执行上下文对象(虚拟的,存在于栈中)
② 对局部数据进行预处理(预解析):

  1)形参变量==>赋值(实参)==>添加为执行上下文的属性
  2)arguments==>赋值(实参列表),添加为执行上下文的属性
  3)var定义的局部变量==> undefined,添加为执行上下文的属性
  2)function声明的函数==>赋值(fun),添加为执行上下文的方法
  3)this==>赋值(调用函数的对象)

③ 开始执行函数体代码

2.3 代码体验

<script>
    // 1. 全局执行上下文--window
    console.log(a1); // undefined
    console.log(window.a1); // // undefined
    console.log(a1 === window.a1); // true,说明全局执行上下文window对var定义的变量a1进行了预处理,将其添加为它的属性
    a2(); // a2()调用成功
    console.log(a2() === window.a2()); //true,说明把function声明的函数a2添加为window的方法
    console.log(this); // window对象,对this赋值

    var a1 = 3;
    function a2() {
        console.log('a2()调用成功');
    };
    console.log(a1); // 3, 开始执行全局代码之后,此时的a1被赋值为3,当然其依旧为window的属性

    // 2. 函数执行上下文
    function fn(a1) {
        // 预处理阶段是在调用函数后,执行函数体前进行
        console.log(a1); // 2,把实参的值赋给形参
        console.log(a2);  // undefined,var定义的局部变量a2添加为执行上下文的属性
        a3(); // a3()调用成功
        console.log(this);  // window,因为此函数是直接调用的,所以调用者为window
        console.log(arguments);  // 伪数组[2,3]
        
        var a2 = 4;
        function a3() {
            console.log('a3()调用成功');
        };
    };
    fn(2, 3);
</script>

3. 执行上下文栈

① 在全局代码执行前JS引擎就会创建一个栈存储管理所有的执行上下文对象
② 在全局执行上下文(window)确定后,将其添加到栈中(压栈),window永远放在栈的最底层
③ 在函数执行上下文创建后,将其添加到栈中(压栈)
④ 在当前函数执行完后,将栈顶的对象移除(出栈)
③ 当所有的代码执行完后,栈中只剩下window

 3.1 代码体验

<script>
                            // 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函数执行上下文
</script>

执行上下文栈中的执行顺序图:

注意:

① 每调用一次函数,就会对应产生这个函数的执行上下文对象;

② 函数执行完函数体代码后,就会被销毁,即从内存中消失,也就不再存在于上下文执行栈中,即出栈。

猜你喜欢

转载自blog.csdn.net/JJ_Smilewang/article/details/125696100