• 第四节:JS中的this指向详解(规则总结、优先级分析、特殊情况)和面试题剖析


    一. 四大规则总结

    1. 背景说明

    (1). 为什么需要this?

       从某些角度来说, 开发中如果没有this, 很多的问题我们也是有解决方案, 但是没有this, 会让我们编写代码变得非常的不方便。
    var obj1 = {
      name: "ypf1",
      eating: function () {
        console.log(obj1.name + "在吃东西");
      },
      running: function () {
        console.log(obj1.name + "在跑步");
      },
      studying: function () {
        console.log(obj1.name + "在学习");
      },
    };
    var obj2 = {
      name: "ypf2",
      eating: function () {
        console.log(this.name + "在吃东西");
      },
      running: function () {
        console.log(this.name + "在跑步");
      },
      studying: function () {
        console.log(this.name + "在学习");
      },
    };
    
    // 调用
    obj1.eating();
    obj2.eating();
    View Code

    (2). this的全局指向?

    /* 
        在大多数情况下,this都是出现在函数中的。
        在全局作用域下:
        1. 浏览器中运行,this是window
        2. node环境中,this是空对象 {}
    */
    
    console.log(this);

    (3). 抛砖引玉

      this指向什么,与函数所在的位置无关,与函数被调用的方式有关系, 从而引出后续的几种绑定规则。
    function foo() {
      console.log(this);
    }
    
    // 1. 直接调用 (指向window)
    foo();
    
    //2. 创建1个对象,对象中的函数指向foo
    var obj1 = {
      name: "ypf",
      foo: foo,
    };
    obj1.foo(); //指向obj1
    
    //3. apply调用
    foo.apply("abc"); //指向abc
    View Code

    2. 规则1-默认绑定

       默认绑定:也就是独立函数的调用,this都是指向window

       何为独立函数调用?  我们可以理解成函数没有绑定到某个对象上的调用; 即光秃秃的1个函数, foo()

       特别说明:与函数定义的位置没有关系,只看调用位置

    // 1. 案例1 (指向window)
    {
      function foo() {
        console.log(this);
      }
      foo();
    }
    
    // 2. 案例2 (指向window)
    {
      function foo1() {
        console.log("-----------------------", this);
      }
      function foo2() {
        console.log("-----------------------", this);
        foo1();
      }
      function foo3() {
        console.log("-----------------------", this);
        foo2();
      }
      foo3(); //输出了三个window对象
    }
    
    //3. 案例3
    {
      var obj = {
        name: "ypf",
        foo: function () {
          console.log(this);
        },
      };
      var bar = obj.foo;
      bar(); //window (说明只与调用位置有关)
    }
    
    //4. 案例4
    {
      function foo() {
        console.log(this);
      }
      var obj = {
        name: "ypf",
        foo: foo,
      };
      var bar = foo;
      bar(); //window (说明只与调用位置有关)
    }
    
    // 5. 案例5
    {
      function foo() {
        function bar() {
          console.log(this);
        }
        return bar;
      }
      var fn = foo();
      fn(); //window (说明只与调用位置有关)
    }
    View Code

    3. 规则2-隐式绑定

      this指向这个对象obj

        比较常见的调用方式是通过某个对象进行调用的:也就是它的调用位置中,是通过某个对象发起的函数调用。

        即:Object.fn(),  Object对象就会被js引擎绑定到fn函数中的this里面

    // 公用函数foo
    function foo() {
      console.log(this);
    }
    
    // 案例1
    {
      console.log("-------------案例1---------------");
      var obj = {
        name: "ypf",
        foo: foo,
      };
      obj.foo(); //this指向obj  { name: 'ypf', foo: [Function: foo] }
    }
    
    // 案例2
    {
      console.log("-------------案例2---------------");
      var obj = {
        name: "ypf",
        eating: function () {
          console.log(this, this.name);
        },
      };
      obj.eating(); //this指向obj  { name: 'ypf', foo: [Function: foo] }
      {
        var fn = obj.eating;
        fn(); //这种模式属于独立函数调用,this指向window,this.name拿不到值,undefined
      }
    }
    
    // 案例3
    {
      console.log("-------------案例3---------------");
      var obj1 = {
        name: "obj1",
        foo: function () {
          console.log(this);
        },
      };
      var obj2 = {
        name: "obj2",
        bar: obj1.foo,
      };
      obj2.bar(); //this指向obj2,充分说明哪个对象点出来的,this就指向哪个对象
    }
    View Code

    4. 规则3-apply/call/bind显式绑定

     显式绑定 (call、apply绑定谁,则this就指向谁), call和apply在执行函数时,是可以明确的绑定this, 这个绑定规则称之为显示绑定。
    // 前置函数foo和obj对象
    function foo() {
      console.log(this);
    }
    var obj = {
      name: "ypf",
    };
    
    // 案例1. call和apply可以直接指向this的绑定对象
    {
      console.log("--------------------案例1------------------------");
      foo.call(); //不指定的时候,this指向window
      foo.apply(); //不指定的时候,this指向window
      // 下面绑定对象
      foo.call(obj); //this指向obj
      foo.apply(obj); //this指向obj
      foo.call("abc"); //this指向abc
      foo.apply("abc"); //this指向abc
    }
    
    // 案例2. call和apply用法的区别
    /* 
      call:传递参数的形式和被调用函数的形式相同
      apply:以数组的形式传递进去
    */
    
    {
      console.log("--------------------案例2------------------------");
      function sum(num1, num2, num3) {
        console.log(num1 + num2 + num3, this);
      }
      sum.call("ypf1", 10, 20, 30); //this指向ypf1,打印结果为60
      sum.apply("ypf2", [10, 20, 30]); //this指向ypf2,打印结果为60
    }
    
    // 案例3. bind绑定
    {
      console.log("--------------------案例3------------------------");
      foo.bind("ypf1"); //仅绑定了,没有调用,没有输出
      let newFoo = foo.bind("ypf2");
    
      //下面属于 默认绑定和显式绑定冲突,优先级:【显式绑定】
      newFoo(); //this指向ypf2
    }
    View Code

    5. 规则4-new绑定 

    (this = new 创建出来的对象)

      JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。 使用new关键字来调用函数是,会执行如下的操作:

      a.创建一个全新的对象;

      b.这个新对象会被执行prototype连接;

      c.这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);

      d.如果函数没有返回其他对象,表达式会返回这个新对象;

    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    
    var p1 = new Person("ypf", 18);
    console.log(p1.name, p2.name);
    View Code

    二. 优先级总结

        1. 默认绑定的优先级最低:当存在其它规则的时候,会通过其它规则来绑定this

        2. 显式绑定优先级高于隐式绑定

        3. new绑定到优先级高于隐式绑定

        4. new绑定到优先级高于显式绑定中的bind (但是new绑定不允许和call、apply同时使用)

      总结: new绑定 > 显示绑定(apply/call/bind) > 隐式绑定(obj.foo()) > 默认绑定(独立函数调用)

     代码分享:

    // 案例1
    /* 
        1. call、apply的显式绑定高于隐式绑定
        2. bind优先级高于隐式绑定
    */
    {
      console.log("------------------案例1---------------------");
      let obj = {
        name: "ypf",
        foo: function () {
          console.log(this);
        },
      };
      //call、apply的显式绑定高于隐式绑定
      obj.foo.call("ypf1"); //this指向ypf1
      obj.foo.apply("ypf2"); //this指向ypf2
      //bind优先级高于隐式绑定
      let test = obj.foo.bind("ypf3");
      test(); //this指向ypf3
    }
    // 更明显的比较
    {
      function foo() {
        console.log(this);
      }
      let obj = {
        name: "ypf",
        foo: foo.bind("ypf6"),
      };
      obj.foo(); //this指向ypf6
    }
    
    // 案例2
    /* 
        new绑定的优先级高于隐式绑定
    */
    {
      console.log("------------------案例2---------------------");
      let obj = {
        name: "ypf7",
        foo: function () {
          console.log(this);
        },
      };
      var f = new obj.foo(); //返回的是foo函数对象
    }
    
    // 案例3
    /* 
        new的优先级高于bind
    */
    {
      console.log("------------------案例3---------------------");
      function foo() {
        console.log(this);
      }
      let bar = foo.bind("aaaa");
      let obj = new bar(); //this指向foo函数对象
    }

    三. 特殊情况总结

    1. 一些函数中的this分析

    (1). setTimeout

    /* 
        this指向window
    */
    
    setTimeout(function () {
      console.log(this); //指向window
    }, 1000);
    View Code

    (2). 一些数组中的方法

    {
      var names = ["abc", "cba", "nba"];
      names.forEach(function (item) {
        console.log(item, this); //this是window
      });
      names.forEach(function (item) {
        console.log(item, this); //this就是abc了
      }, "abc");
    }
    View Code

    (3). 点击监听

    {
      const boxDiv = document.querySelector(".box");
      boxDiv.onclick = function () {
        console.log(this);
        // 这里的this是boxDiv对象,函数内部相当于 boxDiv.click(),相当于隐士绑定
      };
    }
    View Code 

    2. 忽略显式绑定

     apply/call/bind: 当传入null/undefined时, 自动将this绑定成全局对象。
    {
      console.log("----------------案例1-------------------");
      function foo() {
        console.log(this);
      }
      foo.apply("abc"); // 典型的显式绑定,this指向abc
      foo.apply(null); //this指向全局对象window
      foo.call(undefined); //this指向全局对象window
      let bar = foo.bind(null); //this指向全局对象window
      bar();
    }

    3. 间接函数引用

     this指向window
    {
      console.log("----------------案例2-------------------");
      let obj1 = {
        name: "obj1",
        foo: function () {
          console.log(this);
        },
      };
      let obj2 = {
        name: "obj2",
      };
      (obj2.bar = obj1.foo)(); //this指向window
    }

    4. 箭头函数的引用

     不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this。
    // 补充一个箭头函数的简写方式
    // 如果一个箭头函数, 只有一行代码, 并且返回一个对象,返回的对象需要加个()
    {
      var bar1 = () => {
        return { name: "why", age: 18 };
      };
      var bar2 = () => ({ name: "why", age: 18 });
    }
    
    // 案例3
    {
      console.log("----------------案例3-------------------");
      let foo = () => {
        console.log(this);
      };
      foo(); //this指向一个空对象 {}
      foo.call("abc"); //this指向一个空对象 {}
      let obj = {
        name: "ypf",
        foo: foo,
      };
      obj.foo(); //this指向一个空对象 {}
    }
    // 箭头函数中this的实际应用
    {
      var obj = {
        data: [],
        getData: function () {
          // 发送网络请求, 将结果放到上面data属性中
    
          // 在箭头函数之前的解决方案
          // var _this = this
          // setTimeout(function() {
          //   var result = ["abc", "cba", "nba"]
          //   _this.data = result
          // }, 2000);
    
          // 箭头函数之后(不绑定this,就去外层作用域(也就是getData中的this),也就是obj)
          setTimeout(() => {
            var result = ["abc", "cba", "nba"];
            this.data = result;
          }, 2000);
        },
      };
    
      obj.getData();
    }
    View Code

    四. 面试题剖析

    1. 面试题1

     {
      console.log("------------------------面试题1-----------------------------");
      var name = "ypf"; //相当于给windows对象中加了个name属性
      var person = {
        name: "person",
        sayName: function () {
          console.log(this.name);
        },
      };
    
      function sayName() {
        var sss = person.sayName;
        sss(); //this指向window,this.name为ypf(默认绑定)
        person.sayName(); //this指向person (隐式绑定)
        person.sayName(); // (person.sayName)();   this指向person (隐式绑定)
        (b = person.sayName)(); //this指向window,this.name为ypf 赋值表达式(独立函数调用)
      }
      sayName();
    }

    2. 面试题2

    {
      console.log("------------------------面试题2-----------------------------");
      var name = "ypf"; //相当于给windows对象中加了个name属性
      var person1 = {
        name: "person11",
        foo1: function () {
          console.log(this.name);
        },
        foo2: () => console.log(this.name),
        foo3: function () {
          return function () {
            console.log(this.name);
          };
        },
        foo4: function () {
          return () => {
            console.log(this.name);
          };
        },
      };
      var person2 = { name: "person22" };
    
      person1.foo1(); //this指向person1,this.name等于person11  (隐式绑定)
      person1.foo1.call(person2); //this指向person2 ,this.name等于person22  (显式绑定优先级高于隐式绑定)
    
      person1.foo2(); //箭头函数this指向外层作用域就是window,this.name等于ypf
      person1.foo2.call(person2); //this指向window,this.name等于ypf  (error过)
    
      person1.foo3()(); //独立函数调用,this指向window,this.name等于ypf
      person1.foo3.call(person2)(); //独立函数调用,this指向window,this.name等于ypf
      person1.foo3().call(person2); //call优先级高于隐式绑定,this指向person2,this.name等于person22
    
      person1.foo4()(); //箭头函数不绑定this,指向外层作用域person1, 所以this.name等于'person11'    (error过)
      person1.foo4.call(person2)(); //箭头函数不绑定this,外层作用域this被显式绑定了person2,所以this.name等于 person22  (error过)
      person1.foo4().call(person2); // 箭头函数不绑定this,指向外层作用域person1, 所以this.name等于'person11'  (error过)
    }

    3. 面试题3

    {
      console.log("------------------------面试题3-----------------------------");
      var name = "ypf"; //相当于给windows对象中加了个name属性
      function Person(name) {
        this.name = name;
        (this.foo1 = function () {
          console.log(this.name);
        }),
          (this.foo2 = () => console.log(this.name)),
          (this.foo3 = function () {
            return function () {
              console.log(this.name);
            };
          }),
          (this.foo4 = function () {
            return () => {
              console.log(this.name);
            };
          });
      }
    
      var person1 = new Person("person11");
      var person2 = new Person("person22");
    
      person1.foo1(); //person11 (new的优先级高)
      person1.foo1.call(person2); //person22 (显式高于隐式)
    
      person1.foo2(); //person11 (箭头函数,this指向上层作用域 person11)
      person1.foo2.call(person2); //person11   (箭头函数,this指向上层作用域 person11)
    
      person1.foo3()(); //ypf  (独立函数调用,this指向window)
      person1.foo3.call(person2)(); //window
      person1.foo3().call(person2); //person22
    
      person1.foo4()(); //person11
      person1.foo4.call(person2)(); //person22
      person1.foo4().call(person2); //person11
    }

    4. 面试题4 

    {
      console.log("------------------------面试题4-----------------------------");
      var name = "ypf"; //相当于给windows对象中加了个name属性
    
      function Person(name) {
        this.name = name;
        this.obj = {
          name: "obj",
          foo1: function () {
            return function () {
              console.log(this.name);
            };
          },
          foo2: function () {
            return () => {
              console.log(this.name);
            };
          },
        };
      }
    
      var person1 = new Person("person11");
      var person2 = new Person("person22");
    
      person1.obj.foo1()(); //ypf (独立函数调用)
      person1.obj.foo1.call(person2)(); // ypf
      person1.obj.foo1().call(person2); //person22
    
      person1.obj.foo2()(); // obj
      person1.obj.foo2.call(person2)(); //person22
      person1.obj.foo2().call(person2); // obj
    }

     

     

    !

    • 作       者 : Yaopengfei(姚鹏飞)
    • 博客地址 : http://www.cnblogs.com/yaopengfei/
    • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
    • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    php中curl类常用方法封装和详解
    一个简单的PHP的CURL类
    PHP的curl常用的5个例子
    PHP封装CURL扩展
    马老师的WoTou生产消费线程讲解例子
    URL路径设置----第二章:创建和管理内容
    浅谈js设计模式之迭代器模式
    浅谈js设计模式之代理模式
    浅谈js设计模式之策略模式
    浅谈js设计模式之单例模式
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/15880283.html
Copyright © 2020-2023  润新知