js--this指向的相关问题

前言

  关于this的指向问题是前端面试中常考的知识点,也是我们开发学习中较难理解的问题。作为JavaScript的基础,需要我们彻底理解这一关键词。this作为JavaScript中非常复复杂的机制,值得我们付出更大的代价来学习理解。这里分享一下我的学习笔记。

正文

  1.this是什么?this指向什么?

  学习this之前首先要知道this永远指向一个对象,this就是函数运行时候所在的环境,我们在学习执行上下文的时候提到,每一个执行上下文都包含三个重要对象,分别是变量对象、作用域链和 this,JavaScript支持运行环境动态切换,也就是说,this的指向是动态的,并不是固定的指向一个对象,this的指向完全取绝于函数调用的位置。接下来我们看下下面的代码:

        var a=1
        function foo(){
            var a=3
            console.log(a)
        }
        foo()//3 

  上面的代码执行用到函数的作用域问题,foo()函数执行的时候,函数内部定义一个变量a等于3,然后输出打印a,此时foo上下文中存在a变量,因此输出3,若函数foo()内部没有定义a这个变量,在执行打印这句代码的时候,函数内部找不到该变量,此时会沿着作用域链向上次查找,即全局作用域,此时打印结果便为1。

        var a=1
        function foo(){
            var a=3
            console.log(this.a)
        }
        foo()//1

  上面的代码执行结果会打印出1,对比第一段代码你会发现就这里多了this这一个关键词,为什么加了this之后输出结果会发生改变呢?莫非此时this指向window全局对象?

        var a=1
        function foo(){
            var a=3
            console.log(this.a)
        }
        var obj={a:100,foo:foo}
        obj.foo()//100

 

  再来看下上面的代码,打印结果变为100,对比前面两段代码,foo函数增加了obj对象的一层引用,输出的this.a结果再次发生改变?难道此时this指向obj对象?

    this的指向为什么会发生改变,this的指向到底什么时候发生的?是因为函数在JavaScript中既可以当作值传递和返回,也可以当作对象和构造函数。所有函数在运行的时候才能确定其当前的运行环境,所以this就产生了,this会根据函数的运行环境的改变而改变,同时,函数中的this也只能在函数运行时最终确定其运行环境。

  2.this不指向它自身。

    function foo(num) { 
        console.log( "foo: " + num ); // 记录 foo 被调用的次数
        this.count++; 
    }
    foo.count = 0;
    for (var i=0; i<5; i++) {
            foo( i ); 
    }
    console.log("foo.count:"+foo.count)
    //foo: 0
    //foo: 1
    //foo: 2
    //foo: 3
    //foo: 4
    //foo.count:0

  上面的代码打印输出foo:01234,说明foo函数被调用了五次,然后打印foo.count输出还是0,说明this.count中的this并不是指向的函数foo本身,foo.count只是函数foo的一个属性,this无法指向他本身。我们可以通过arguments.callee来引用当前正在执行的函数对象,即对象本身。

  3.this不指向它的作用域。

  另一种常见的误解就是this指向函数的作用域。我们需要牢记,this在任何情况下都不指向函数的词法作用域,this的指向完全取决于函数调用的位置。因此不能使用this来引用一个函数词法作用域内部的东西。

  4.this的绑定规则。

  this是什么?当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的 其中一个属性,会在函数执行的过程中用到。
        如何寻找函数的调用位置,从而判断函数在执行过程中会如何绑定 this?首先找到调用位置:使用开发者工具得到调用栈,然后找到栈中第二个元素,这就是真正的调用位置。

  通过函数调用的位置来确定函数绑定的this对象,需要遵守以下四条规则:

  (1)默认绑定

            function foo() { 
                    console.log( this.a ); 
             }
             var a = 2; 
             foo(); // 2    

  上面的代码中,foo()函数直接调用,不带任何修饰的函数引用进行调用,因此采用默认绑定,此时this指向window对象,无法应用其他规则。在非 strict mode 下时,默认绑定才能绑定到全局对象,严格模式下 this 绑定在undefined。

  (2)隐式绑定--需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。

                function foo() { 
                    console.log( this.a ); 
                }
                var obj = { a: 2, foo: foo };
                obj.foo(); // 2

  上面的代码中,foo函数被调用时 obj 对象 “ 拥有 ” 或者 “ 包含 ” 它。当函数引 用有上下文对象时,隐式绑定规则会把函数调用中的  this绑定到这个上下文对象,因为调 用 foo() 时 this 被绑定到 obj,因此 this.a 和 obj.a 是一样的。

            function foo() {
                    console.log(this.a);
                }
                var obj2 = {
                    a: 2,
                    fn: foo
                };
                var obj1 = {
                    a: 1,
                    o1: obj2
                };
                obj1.o1.fn();//2

  上面的代码需要值得注意的是:对象属性引用链中只有最顶层或者说最后一层会影响调用位置。

              function foo() { 
                    console.log( this.a ); 
                }
                var obj = { a: "local", foo: foo };
                var bar = obj.foo; // 函数别名!
                var a = " global"; // a 是全局对象的属性 
                bar(); // "global"

  上面的代码,虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

          function foo(){
                    console.log(this.a)
                }
                var a="global"
                var obj={a:"local",foo:foo}
                var bar=obj.foo
                function doFnc(fn){
                    fn()
                }
                doFnc(bar)//global

  上面的代码需要注意的是:被隐式绑定的函数会丢失绑定对象。

  (3)显示绑定或者硬绑定--call,apply,bind直接指定this的绑定对象 

  call() apply() bind()直接指定 this 的绑定对象,返回一个硬编码的新函数,它会把参数设置为 this 的上下文并调用原始函数。但是如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值 在调用时会被忽略,实际应用的是默认绑定规则。foo.call( obj );以在调用 foo 时强制把它的 this 绑定到 obj 上

  (4)new绑定

  JavaScript 中 new 的机制实 际上和面向类的语言完全不同。

  在 JavaScript 中,构造函数只是一些 使用 new 操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上, 它们甚至都不能说是一种特殊的函数类型,它们只是被 new 操作符调用的普通函数而已。包括内置对象函数(比如 Number(..))在内的所有函数都可 以用 new 来调用,这种函数调用被称为构造函数调用。

       使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。 

                1. 创建(或者说构造)一个全新的对象。

                2. 这个新对象会被执行 [[ 原型 ]] 连接。 

                3. 这个新对象会绑定到函数调用的 this。

                4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象

  new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。

  5.绑定的优先和一些特殊情况。

  判断优先级:
            (1)函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。 var bar = new foo() 
            (2)函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是 指定的对象。 var bar = foo.call(obj2) 
            (3)函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上 下文对象。 var bar = obj1.foo() 
            (4)如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到 全局对象。 var bar = foo()
  特殊情况:

  (1)间接绑定

         function foo() { console.log( this.a ); }
                var a = 2;
                var o = { a: 3, foo: foo };
                var p = { a: 4 }; 
                o.foo(); // 3 
                (p.foo = o.foo)(); // 2
                赋值表达式 p.foo = o.foo 的返回值是目标函数的引用

  对比隐式绑定

          function foo() { console.log( this.a ); }
                var a = 2;
                var o = { a: 3, foo: foo };
                var p = { a: 4 ,fo:o.foo};
                p.fo() //4   对比间接引用

  (2)事件绑定中的this

          1.行内绑定 <input type='button' onclick="this">
                          <input onclick="clickFuc"> function(){  this ...}
                          //this指向window对象
                2.事件监听与动态绑定
                        <input type="button" value="按钮" id="btn">
                        <script> var btn = document.getElementById('btn');
                            btn.onclick = function(){
                                this ;  // this指向本节点对象
                            }</script>
                 //动态绑定的事件本身就是对应节点的一个属性,重新赋值为一个匿名函数,this自然就指向了本节点对象

  (3)箭头函数

  ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。这其实和 ES6 之前代码中的 self = this 机制一样。

总结

  以上就是本文的全部内容,希望给读者带来些许的帮助和进步,方便的话点个关注,小白的成长之路会持续更新一些工作中常见的问题和技术点。

 

 

 

posted @ 2021-03-07 16:47  zaisy'Blog  阅读(230)  评论(0编辑  收藏  举报