[ JavaScript ] this 指向相关问题

136 阅读6分钟

JavaScript 中的 this 指向相关问题

1、关于 this 的指向

  • JavaScript 在执行函数时,会给 this 绑定一个值

  • this 的绑定和函数定义的位置没有关系,只和调用方式调用位置有关

  • this 的绑定的值是在函数执行的时候才被确定的

2、关于 this 的绑定规则

  • 规则一 默认绑定

    • 独立函数调用时,在非严格模式下,this 的指向是 window,严格模式下是 undefined

            // 1. 普通函数
            function foo() {
              console.log("foo", this);
            }
            foo(); // window
      
            //  2. 定义在对象中的函数
            const obj = {
              foo: function () {
                console.log("obj", this);
              },
            };
            const bar = obj.foo;
            bar(); //window
      
            // 3. 高阶函数
            function text(fn) {
              fn();
            }
            text(obj.foo); //window
      
  • 规则二 隐式绑定

    • 当函数被某个对象调用时,浏览器引擎自动实现的绑定,将 this 指向这个对象

            function foo() {
              console.log("foo", this);
            }
            const obj = {
              name: "obj",
              bar: foo,
              baz: function () {
                console.log("baz", this);
              },
            };
      
            const obj2 = {
              name: "obj2",
              baz: obj.baz,
              obj,
            };
      
            obj.bar(); // obj
            obj.baz(); // obj
      
            obj2.obj.bar(); // obj
            obj2.obj.baz(); // obj
            obj2.baz(); // obj2
      
  • 规则三 显式绑定

    • apply、call

            function foo(...args) {
              console.log("foo", this, ...args);
            }
      
            foo(); // window
      
            foo.call(123, 1, 2, 3); // Number(123), 1 2 3
            foo.apply("apply", [1, 2, 3]); // String(apply), 1 2 3
      
            foo.call({ name: "apply", age: 18 }); // {apply:'apply'}
            foo.apply({}); // {}
      
    • bind MDN文档:developer.mozilla.org/zh-CN/docs/…

            // bind 方法创建了一个新的绑定函数(BF, bound function)
            // 绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语)
            // 在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用
            // MDN 文档解释:
            // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
      
            const obj = {
              name: "bind",
            };
            const bar = foo.bind(obj, 1, 2, 3);
            const baz = foo.bind(123, 1, 2, 3);
      
            bar(); // { name: "bind" }, 1, 2, 3
            bar(4, 5, 6); // { name: "bind" }, 1, 2, 3, 4, 5, 6
      
            baz(); // Number(123), 1, 2, 3
      
    • 显式绑定中的细节

            // 在非严格模式下,显式指定的 this 如果一个基本类型,且该基本类型有对应的包装类,则会自动转换为该包装类型
            // 如果没有该基本类型没有对应的包装类型,则会指向 window
            // 在严格模式下,则不会进行包装类型的转换, 传入的 this 参数是啥,this 指向就是啥
      
            // 非严格模式
            foo.call(undefined, [1, 2, 3]); // Window, 1 2 3
            foo.apply(null, [1, 2, 3]); // Window, 1 2 3
            foo.call(123); // Number(123)
            // 严格模式
            foo.call(undefined, [1, 2, 3]); // undefined, 1 2 3
            foo.apply(null, [1, 2, 3]); // null, 1 2 3
            foo.call(123); // 123
      
             // 关于包装类
            const num = new Number(1);
            const str = new String("123");
            const flag = new Boolean(true);
            console.log(num, typeof num, "--number"); // Number(1) 'object' '--number'
            console.log(str, typeof str, "--string"); // String('123') 'object' '--string'
            console.log(flag, typeof flag, "--boolean"); // Boolean(true) 'object' '--boolean'
      
            // 对象
            const newObj = new Object();
            console.log(newObj, typeof newObj, "--object"); // {} 'object' '--object'
      
  • 规则四 new 绑定

    • JavaScript 中的函数可以当做一个类的构造函数来使用,也就可以使用 new 关键字

            // 使用 new 关键字来调用函数时,会执行四步操作
            // 1. 创建一个新的空对象
            // 2. 这个新对象会被执行prototype连接
            // 3. 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成)
            // 4. 如果函数没有返回其他对象,表达式会返回这个新对象
      
            function Persion(name) {
              console.log(this, "开始执行函数体"); // Persion{}
              this.name = name;
            }
            const student = new Persion("小明");
            console.log(student); // Persion{ name: "小明" }
      

3、关于 this 绑定规则优先级

  • 默认绑定优先级最低

  • 显式绑定高于隐式绑定

  • 显式绑定中 bind 绑定高于 apply 和 call 绑定

  • new 绑定高于隐式绑定

  • new 绑定高于bind 绑定(new 绑定不能和 apply 和 call 一起使用)

          "use strict";
          function foo() {
            console.log("foo", this);
          }
    
          // 1. 显式绑定高于隐式绑定
          const obj_0 = {
            name: "obj1",
            foo: foo,
          };
          obj_0.foo.call("callThis"); // callThis
    
          // 2.bind 绑定高于隐式绑定
          const bar = foo.bind("BindThis");
          const obj_1 = {
            name: "obj_1",
            bar,
          };
          obj_1.bar(); // BindThis
    
          // 3. bind 绑定高于 call 和 apply 绑定
          bar.call("callThis"); // bindThis
          bar.apply("callThis"); // bindThis
    
          // 4. new 绑定高于隐式绑定
          const obj_2 = {
            name: "obj_2",
            obj_2_foo: function () {
              console.log("obj_2_foo", this, this === obj_2);
            },
          };
          new obj_2.obj_2_foo(); // obj_2_foo obj_2_foo{} false
    
          // 4. new 绑定高于显式绑定(new 绑定不能和 call 和 apply 绑定一起使用)
          new bar(); // foo foo{}
    

4、关于 this 绑定规则之外的情况

  • 忽略显式绑定

  • 间接函数引用

  • 箭头函数

          // 1. 显式绑定中指定 this 为 null 或者 undefinde 时,使用默认绑定规则
          function foo() {
            console.log(this);
          }
    
          foo.call(null); // 严格模式: null 非严格模式: Window
          foo.call(undefined); // 严格模式: undefined 非严格模式: Window
    
          // 2. 创建一个函数的间接引用, 这种情况使用默认绑定规则
          const obj_1 = {
            name: "obj_1",
            foo,
          };
          const obj_2 = {
            name: "obj_2",
          };
          obj_1.foo(); // {name: 'obj_1', foo:f}
          // TIP: 以小括号中括号等开始的代码一定要注意分号的使用,在上一行代码结束加上分号,或者在代码开始前加上分号
          (obj_2.foo = obj_1.foo)(); // 严格模式下: undefined 非严格模式下: Window
    
          // TIP: 关于箭头函数(Arrow Function)
          // 1 普通函数中都会有 this 标识符, 但是在箭头函数中压根没有 this
          // 2 箭头函数不会绑定 this、arguments 属性
          // 3 箭头函数不能作为构造函数来使用(不能和 new 一起使用, 会抛出错误)
          // 4 箭头函数的编写优化:
          //  4.1 只有一个参数时,小括号可以省略
          //  4.2 如果函数执行体只有一行代码,那么大括号可以省略,且这行代码的返回值作为整个函数的返回值
          //  4.3 如果函数执行体只有返回一个对象, 那么需要给这个对象加上()
    
          // 3. 箭头函数是根据外层作用域来决定 this 的绑定值的
          // TIP: let 和 const 声明的变量不在顶层对象中,而在 script 块儿级作用域中
          var flag = "varGlobal";
          const const_flag = "constGlobal";
          window.windowFlag = "windowFlag";
          console.log(window.flag); // varGlobal
          console.log(window.const_flag); // undefined
          console.log(window.windowFlag); // windowFlag
    
          const bar = () => {
            console.log(this.flag);
          };
    
          const obj_3 = {
            flag: "obj_2",
            bar,
            baz: function () {
              const flag = "bazFlag";
              const foo = () => {
                console.log(this.flag);
              };
              return foo;
            },
          };
    
          bar(); // varGlobal
          bar.call({ flag: "callFlag" }); // varGlobal
          obj_3.bar(); //varGlobal
    
          const fn1 = obj_3.baz();
          fn1(); // obj_3  foo -> baz
          fn1.call({ flag: "callFlag" }); // obj_3
    
          // 思考:箭头函数中 this 的应用
          // 1. 嵌套函数时,内部函数需要访问外部函数变量时
          // 等等...
    

5、关于 this 在内置函数中的指向

  • setTimeout,forEach,onclick 等类似的内置函数,其调用方式如何确定呢?

  • 通过经验,总结,猜测判断

  • forEach 可以传入第二个参数,显式绑定 this

          // "use strict";
          var flag = "varGlobal";
          // 1. onclick 中this 绑定规则的推测  => 推测为 默认绑定
          const box = document.getElementById("divId");
          box.onclick = () => {
            const flag = "boxGlobal";
            console.log(this, this.flag); // Window varGlobal
          };
          // box.onclick = function () {
          //   const flag = "boxGlobal";
          //   console.log(this, this.flag); // box元素  undefined
          // };
    
          box.onclick.call({ flag: "callThis" }); // { flag: "callThis"} callThis
    
          // 2.setTimeout 中 this 绑定规则的推测  => 推测是通过 apply 或 call 显式绑定的 Window
          const obj = {
            flag: "objFlag",
            foo: function () {
              console.log(this, this.flag);
            },
          };
          const baz = obj.foo.bind({ flag: "bindFlag" });
          const fn = new baz();
          setTimeout(obj.foo); // Window  严格模式下也为 Window
          setTimeout(baz); // bindFlag
    
          // 3. forEach 中 this 绑定规则的推测  => 推测是通过 apply 或 call 显式绑定的全局 this
          // 且第二个参数是通过call 或者 apply 来显式绑定的
          const obj_2 = {
            flag: "obj2Flag",
            foo: function (item) {
              console.log(item, this);
            },
            baz: (item) => {
              console.log(item, this);
            },
          };
          const fn1 = obj_2.foo.bind("bindThis");
          [1].forEach(obj_2.foo); // Window  严格模式下为 undefined
          [1].forEach(obj_2.baz); // Window  严格模式下为 Window
          [1].forEach(fn1); // String(BindThis)
          [1].forEach(obj_2.foo, "customThis"); // String(customThis)
          [1].forEach(obj_2.baz, "customThis"); // Window  严格模式下为 Window
          [1].forEach(fn1, "customThis"); // String(BindThis)