JavaScript语法函数高级之执行上下文和执行上下文栈

本文深入探讨JavaScript的执行上下文和执行上下文栈,包括全局执行上下文和函数执行上下文的区别,作用域与执行上下文的关系,以及this的指向规则。详细解析了执行上下文的创建过程、作用域链、变量提升和函数提升的规则,并对this的四种绑定方式进行了总结,强调了硬绑定解决隐式丢失问题的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、 执行上下文

1、理解执行上下文
广义上来说:
执行上下文一套非常严格的规则
狭义上来说:
执行上下文一个c++对象

2、 定义:执行上下文以一套非常严格的规则,规范变量存储、提升、this指向。
3、分类: 全局执行上下文;函数执行上下文
3、作用:对数据进行预处理
4、如何计算执行上下文环境的个数?

  • n+1
  • n:函数调用的次数,函数每调用一次,开启一个对全局数
  • 1:全局上下文环境
    <!--    
    执行上下文(也称执行上下文环境)
              定义:执行上下文以一套非常严格的规则,用来规范
                         函数体代码执行前,变量声明并赋值(默认为underfined)
                                                         函数声明,函数和变量提升,this赋值等操作。
              分类: 全局执行上下文;函数执行上下文

               什么时候创建执行上下文环境?
                        全局执行上下文:全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
                        函数执行上下文:函数调用时;

              如何计算执行上下文环境的个数?
                        n+1
                         n:函数调用的次数,函数每调用一次,执行上下文环境。
                         1:执行上下文环境      

              注意:执行上下文是附属于自己对应的作用域的
                   一个作用域在一个时刻可以拥有多个执行上下文,当处于活动状态的只有一个

    -->
    <script type="text/javascript">

        function wrap(){
            console.log("函数执行上下文环境")
        }
        wrap();
        wrap();
        wrap();
        wrap();
        wrap();
        //6个执行上下文环境
        console.log("全局执行上下文环境")
    </script>

二、 执行上下文栈

1、 理解压栈

  • 当全局代码开始执行前,先创建全局执行上下文环境
  • 当全局执行上下文环境创建好了以后将上下文中的所有内容放入栈内存
  • 最先放入的在最下边(global)
  • 其他执行的函数的执行上下文依次放入(放入的顺序是代码的执行顺序)
  • 栈中最后放入的执行完最先出栈。
    这里写图片描述

2、 执行上下文栈是执行上下文的活动记录。

<!--    
     执行上下文栈
                               定义: 执行上下文栈是执行上下文的活动记录(数据的出栈&压栈)。
        1. 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
        2. 在全局执行上下文确定后, 将其添加到栈中(压栈)
        3. 在函数执行上下文创建后, 将其添加到栈中(压栈)
        4. 在当前函数执行完后,将栈顶的对象移除(出栈)
        5. 当所有的代码执行完后, 栈中只剩下在全局执行上下文

    一个作用域在一个时刻可以拥有多个执行上下文,当处于活动状态的只有一个
    -->
<script type="text/javascript">

    function wrap() {
        console.log("函数执行上下文环境")
    }

    //      wrapp执行时同时可有5个上下文环境,但处于活动状态的只有一个.
    (function wrapp() {
        var b = 2;

        function inner() {
            var c = 3;

三、作用域和执行上下文

区别1:
1. 除全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时。
2. 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建。
3. 函数执行上下文环境是在调用函数时, 函数体代码执行之前创建。
总结:作用域是在代码编译时确定的, 全局执行上下文环境在代码执行前确定, 函数执行上下文环境在函数体代码执行前创建。

区别2:
1. 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
2. 上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会被释放
总结:
作用域是静态的,上下文环境是动态的。
执行上下文是附属于自己对应的作用域的。

区别3:
一个作用域 在一个时刻可以有多个上下文,处于活动状态的上下文只有一个。

联系
1. 上下文环境(对象)是从属于所在的作用域
2. 全局上下文环境==>全局作用域
3. 函数上下文环境==>对应的函数使用域


<!--    
    作用域&执行上下文的区别与联系:
       区别:
        1、什么时间确定?
                       作用域:
                                   全局作用域&局部作用域都是在代码编写时确定的。
                         执行上下文环境:  
                                   全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
                                    函数执行上下文环境是在调用函数时, 函数体代码执行之前创建  

        2、状态 
                         作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
                               上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会被释放

       联系:
        1. 上下文环境(对象)是从属于所在的作用域
        2. 全局上下文环境==>全局作用域
        3. 函数上下文环境==>对应的函数作用域
        -->
    <script type="text/javascript">

    </script>

四、规则

1、全局执行上下文
在执行全局代码前将window确定为全局执行上下文
对全局数据进行预处理:

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

开始执行全局代码.
2、函数执行上下文
在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
对局部数据进行预处理:

  • 1.形参变量==>赋值(实参)
  • 2.arguments==>赋值(实参列表)
  • 3.处理 var定义的局部变量
  • 4.处理 function声明的函数
  • 5.提升
  • 6.this==>赋值(调用函数的对象)

开始执行函数体代码
3、变量的查找
如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的处于活动状态的执行上下文环境,再在其中寻找变量的值

    <!--    
    执行上下文的规则:
              全局执行上下文
                              在执行全局代码前将window确定为全局执行上下文
                               对全局数据进行预处理
                    1.var定义的全局变量==>undefined, 添加为window的属性
                    2.function声明的全局函数==>赋值(fun), 添加为window的方法
                    3. 提升
                    4.this==>赋值(window)
                               开始执行全局代码
                     函数执行上下文
                               在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
                                          对局部数据进行预处理
                    1.形参变量==>赋值(实参)
                    2.arguments==>赋值(实参列表)
                    3.处理 var定义的局部变量
                    4.处理 function声明的函数
                    5.提升
                    6.this==>赋值(调用函数的对象)
                                          开始执行函数体代码
                     变量的查找
                                如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的处于活动状态的执行上下文环境,再在其中寻找变量的值

-->

    <script type="text/javascript">
    /*全局执行上下文
        1、var a=underfined;
        2、
    */  
        function wrap(b) {
            /*函数执行上下文

           */
            var c = b;
            function inner(b){

            }
        }
        var a = "a";
        wrap(1);
    </script>

五、提升

1、变量提升

  • 通过var定义(声明)的变量, 在定义语句之前就可以访问到
  • 值: undefined

2、 函数提升

  • 通过function声明的函数, 在之前就可以直接调用
  • 值: 函数定义(对象)

3、 提升注意点

  • 函数的提升是整体的提升
  • 变量的提升是声明的提升
  • 函数的提升优于变量的提升
  • 提升是指提升到本层作用域的最顶层

4、注意事项和使用细节

  • 变量的提升不会理会if else 这种条件暗示,会跳出流程,进行提示。
  • 永远不要再流程控制语句的块内部定义函数。
<!--    
    提升:提升都是提升到本层作用域的最顶层
                     变量:声明提升
                     函数:整体提升
                     函数表达式的提升本质是变量的提升。
                     函数提升优于变量提升。
           注意:变量的提升不会理会if else 这种条件暗示,会跳出流程,进行提示。
                      永远不要再流程控制语句的块内部定义函数。
                      -->
    <script type="text/javascript">

        function wrap(b) {

            var c = b;
            function inner(b){

            }
        }
        var a = "a";
        wrap(1);
        /*提升后如下:
         全局:
         function wrap(b) {

            var c = b;
            function inner(b){

            }
        }
        var a = underfined;
         a = "a";
         函数:
         function wrap(b) {
            function inner(b){

            }
            var c = underfined;
            c = b;

        }
         */
    </script>

六、this到底指向谁?

1、之前this使用总结:

<script type="text/javascript">
        /*  之前的this总结
                 new fn()   fn的this--->实例
                 fn() fn的this---> window 
                 fn.call(a) fn的this---> a 
                 fn.apply(b) fn的this---> b 
                 定时器的回调中的this ----> window 
            */
        function wrap() {
            console.log(this)
        }
        setTimeout(function() {
            console.log(this);
        }, 1000)
        new wrap();
        console.log(new wrap())
        wrap();
        wrap.call(a);
        wrap.apply(b)
    </script>

2、全局上下文
无论是否在严格模式下,在全局执行上下文中(在任何函数体外部)this 都指代全局对象。

    <!--    
         全局上下文
                         无论是否在严格模式下,在全局执行上下文中(在任何函数体外部)this 都指代全局对象window。
-->
    <script type="text/javascript">
        console.log(this)
    </script>

3、函数上下文:在函数内部,this的值取决于函数被调用的方式。
* 默认绑定*:独立调用(普通调用):
this 的值默认指向全局对象。
代码在严格模式(“use strict”)下执行,默认this指向underfind

</body>
    <!--
           函数上下文:在函数内部,this的值取决于函数被调用的方式。
                     默认绑定:独立调用(普通调用):
                this 的值默认指向全局对象。
                                           代码在严格模式(“use strict”)下执行,默认this指向underfind
             注意:虽然 this 的绑定规则完全取决于调用位置,但是只有 foo()运行在非 strict mode 下时,默认绑定才能绑定到全局对象;严格模式下调用foo()不会影响默认绑定规则

           总结:无论函数是在哪个作用域中被调用,只要是独立调用则就会按默认绑定规则被绑定到全局对象或者undefined上
   -->
    <script type="text/javascript">
         //独立调用
        function wrap() {
            console.log(this.a)
        }
        var a = 1;
        wrap();

        //函数运行在严格模式中
        "use strict";
        function wrap1() {
            console.log( this.b );
        }
        var b = 2;
        wrap1();


        // 在严格模式下进行函数调用
        var c = 3;
        function wrap2() {
            console.log( this.c );
        }
        (function(){
            "use strict";
            wrap2();
        })()

//      虽然 this 的绑定规则完全取决于调用位置,但是只有 foo()运行在非 strict mode 下时,默认绑定才能绑定到全局对象;严格模式下调用foo()不会影响默认绑定规则
        function foo() {
            console.log( this.a );
        }
        (function(){
            "use strict";
            foo(); 
        })();


        //如果使用严格模式( strict mode ),this 会绑定到 undefine
        function wrap3() {
            "use strict";
            console.log( this.a );
        }
        wrap3(); 
    </script>

隐式绑定

隐式调用(obj.属性):使用隐式绑定规则,this指向离他最近的调用者。
当函数作为对象里的方法被调用时,它们的 this 是调用该函数的对象。

    <!--
          函数上下文:在函数内部,this的值取决于函数被调用的方式。
                     隐式绑定 :隐式调用(obj.属性):使用隐式绑定规则,this指向离他最近的调用者。
                                          当函数作为对象里的方法被调用时,它们的 this 是调用该函数的对象。       
           隐式绑定的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含
    当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象

    对象属性引用链中只有最顶层或者说最后一层会影响调用位置                            
   -->
    <script type="text/javascript">

        function wrap() {
            console.log( this.a );//this指向调用者
        }
        var obj = {
            a: 2,
            foo: wrap
        };
        obj.foo(); 
        //  对象属性引用链中只有最顶层或者说最后一层会影响调用位置
        function wrap1() {
            console.log( this.a );
        }
        var obj2 = {
            a: 42,
            foo: wrap1
        };
        var obj1 = {
            a: 24,
            obj2: obj2
        };
        obj1.obj2.foo(); 
    </script>

显式绑定

可以使用函数的 call(..) 和 apply(..) 方法来实现显示绑定
硬绑定:在一个包裹函数内使用显示绑定去修改一个指定函数的this指向,最终将其返回出来!这种包裹函数对指定函数的约束,我们称之为硬绑定 。

<!--
 函数上下文:在函数内部,this的值取决于函数被调用的方式。
显式绑定:可以使用函数的 call(..) 和 apply(..) 方法来实现显示绑定
硬绑定(bind):在一个包裹函数内使用显示绑定去修改一个指定函数的this指向,最终将其返回出来!这种包裹函数对指定函数的约束,我们称之为硬绑定    。   

   -->
    <!--
    我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数
    具体点说,可以使用函数的 call(..) 和 apply(..) 方法来实现显示绑定

-->
    <script type="text/javascript">
        function wrap(a, b) {
            console.log(this.a, a, b)
        }
        var obj = {
            a: 2
        };
//      call()&apply()的区别:传入参数的不同
        wrap.call(obj, "a", "b");
        wrap.apply(obj, ["c", "d"]);
        wrap.bind(obj)("e", "f")
    </script>

new 绑定:作为构造函数绑定
当一个函数用作构造函数时(使用new关键字),它的this被绑定到正在构造的新对象。

    <!--
   函数上下文:在函数内部,this的值取决于函数被调用的方式。
    new 绑定:作为构造函数绑定
                               当一个函数用作构造函数时(使用new关键字),它的this被绑定到正在构造的新对象。
    JavaScript中的“构造函数”:
            JavaScript,构造函数只是一些使用 new 操作符时被调用的函数。
                                它们并不会属于某个类,也不会实例化一个类。
                                实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被 new 操作符调用的普通函数而已。  
                                实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”
           使用 new 来调用函数,或者说发生构造函数调用时,对于我们的this来说,这个新对象会绑定到函数调用的 this 。
   -->
    <script type="text/javascript">
        /*使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。
              new 是最后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 new 绑定。
        */
        function foo(a) {
            this.a = a;
        }
        var bar = new foo(2);
        console.log(bar.a); // 2
    </script>

this绑定 优先级

new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

4、函数上下文隐式绑定的问题:

1)隐式丢失(以隐式绑定的形式进行传参或者赋值 最终却以其他形式调用函数 导致this指向发生偏差)

 <!--隐式丢失: 以隐式绑定的形式传入,最终以独立调用的方式调用,产生了隐式丢失-->
     <script type="text/javascript">
//   write()函数改变了write的this的指向,让它指向global或window对象,
//          导致执行时提示非法调用异常
        var write=document.write();
        write("1111111111111");
        document.write("2222222222");

     </script>

2)怎么解决隐式丢失?——硬绑定

<script type="text/javascript">

        var write = document.write.bind(document);
        write("123");


        var obj={
            age:18,
            test:function(a,b){
                console.log(this.age,a,b);
            }
        }

        setTimeout(obj.test.bind(obj,1,2),1000);

        /*
        call  apply
                将指定函数中的this绑给他们的第一个参数,并且进行调用!!!
        bind
            将将指定函数中的this绑给他的第一个参数,并且返回一个新的this指向已经定死的硬绑定函数!!
        */
    </script>

5、显式绑定变种_硬绑定
1)在一个包裹函数内使用显示绑定去修改一个指定函数的this指向,最终将其返回出来!这种包裹函数对指定函数的约束,我们称之为硬绑定

<!--    
显式绑定变种_硬绑定如何工作?
           创建函数 bar() ,并在它的内部手动调用了 bar.call(obj) ,
           因此强制把 foo 的 this 绑定到了 obj 。无论之后如何调用函数 bar ,它总会手动
    在 obj 上调用 foo 。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。
    -->
    <script type="text/javascript">
        function foo() {
            console.log(this.a);
        }

        var bar = function() {
            foo.call(obj);
        }
        var obj_test = {
            a: "test"
        };
        var a = 1;
        var obj = {
            a: "a"
        }
        bar();
        setTimeout(bar, 1000);
        bar.call(obj_test); //  硬绑定的bar不可能再修改它的this(指的是foo中的this)
    </script>

2)硬绑定函数_bind函数

<!--
    简单的辅助绑定函数    bind函数的作用:返回一个新的函数,并且指定该新函数的this指向
-->
<script>
    //  类bind函数的实现
    function foo(something, otherthing) {
        console.log(this.a + " " + something + " " + otherthing);
        return this.a + something;
    }

    function bind(fn, obj) {
        return function () {
            return fn.apply(obj, arguments);
        };
    }

    var obj = {
        a: 2
    };
    var obj_test = {
        a: 22
    };

    var bar = bind(foo, obj);
    var b = bar(3); // 2 3 undefined
    console.log(b); // 5
    bar.call(obj_test, 3); //2 3 undefined
</script>

3)典型应用场景
硬绑定的典型应用场景就是创建一个包裹函数,传入所有的参数并返回接收到的所有值

<!--硬绑定的典型应用场景就是创建一个包裹函数,传入所有的参数并返回接收到的所有值-->
    <script>

    function foo(arg1,arg2) {
        console.log( this.a,arg1,arg2);
        return this.a + arg1;
    }
    var obj = {
        a:2
    };
    var bar = function() {
        return foo.apply( obj, arguments);
    };


    var b = bar(3,2); 
    console.log( b ); 
    </script>

6、this污染:
独立调用时,this把函数中方法挂载全局window上,造成全局变量的污染.

    <script type="text/javascript">
        /*
                function app(){
                    function inner(){
                        console.log(inner);
                    }
                    var inner;
                    inner();
                }
                app();
            */
        /*
            this污染:
                  独立调用时,this把函数中方法挂载全局window上,造成全局变量的污染.
            */
        function app2() {
            //this 有没有污染全局环境??
            this.test = function() {

            };
            this.toString = function() {

            }
        }
        app2();
        console.log(window)
        app2.apply(Object.create(null));
        console.log(Object.create(null));
    </script>

6、注意事项和使用细节:

1)虽然 this 的绑定规则完全取决于调用位置,但是只有 foo()运行在非 strict mode 下时,默认绑定才能绑定到全局对象;严格模式下调用foo()不会影响默认绑定规则
2) JavaScript中的“构造函数”:
JavaScript,构造函数只是一些使用 new 操作符时被调用的函数。
它们并不会属于某个类,也不会实例化一个类。
实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被 new 操作符调用的普通函数而已。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值