美文网首页
难缠的this

难缠的this

作者: DiffY | 来源:发表于2018-09-23 00:07 被阅读0次

    在深入了解JavaScript中this关键字之前,有必要先退一步,看一下为什么this关键字很重要。this允许复用函数时使用不同的上下文。换句话说,this关键字允许在调用函数或方法时决定哪个对象才是焦点。之后讨论的所有东西都是基于这个理念。我们希望能够在不同的上下文或在不同的对象中复用函数或方法。

    我们关注的第一件事是如何判断this关键字的引用。当你试图回答这个问题时,需要问自己的第一个也是最重要的问题是“这个函数在哪里被调用”。判断this引用什么的唯一方法就是看使用this关键字的这个方法在哪里被调用

    一 隐式绑定

    请记住,判断this指向要看使用this关键字的这个方法在哪里被调用。假如有下面一个对象

    const obj = {
      title: 'test',
      fn(): {
        console.dir(this.title)
      }
    }
    

    如果想调用fn方法,就要

    obj.fn()
    

    这就把我们带到隐式绑定规则的主要关键点。为了判断 this 关键字的引用,函数被调用时先看一看点号左侧。如果有“点”就查看点左侧的对象,这个对象就是 this 的引用。
    在上面的例子中,obj 在“点号左侧”意味着 this 引用了 obj 对象。所以就好像 在 fn 方法的内部 JavaScript 解释器把 this 变成了 obj。
    再看如下例子

    const obj = {
      title: 'test',
      fn() {
        console.dir(this.title)
      },
      sub: {
        title: 'test2',
        fn() {
          console.dir(this.title)
        }
      }
    }
    obj.fn()   // test
    obj.sub.fn()  // test2
    

    每当判断 this 的引用时,都需要查看调用过程,并确认“点的左侧”是什么。第一个调用,obj 在点左侧意味着 this 将引用 obj。第二次调用中,sub 在点的左侧意味着 this 引用 sub。
    所以大多数情况下,检查使用this方法的点的左侧是什么。如果没有点呢,继续往下看。

    二 显示绑定

    如果fn只是一个独立的函数,如下

    fn() {
      console.dir(this.title)
    }
    const obj = {
      title: 'test'
    }
    

    为了判断this的引用必须先查看这个函数的调用位置,那怎样才能让fn调用的时候将this指向this?我们并不能再像上面那样简单的使用obj.fn(),因为obj并没有fn方法。但是在js中,每个函数都有一个方法call,正好解决这个问题。

    "call" 是每个函数都有的方法,它允许在调用函数时为函数指定上下文

    所以,可以用下面的方式调用fn方法

    fn.call(obj)
    

    call是每个函数都有的属性,并且传递给它的第一个参数会作为函数调用时的上下文。也就是说,this会指向传递给call的第一个参数

    这就是显示绑定的基础,因为我们明确的使用call指定了this的引用。
    现在将fn改动一下

    fn(name1, name2){
      console.dir(`${this.title} is ${name1} and ${name2}`)
    }
    

    此时使用call方法就要如下

    fn.call(obj, '张三', '李四')
    

    使用call方法需要将参数一个一个的传递进去,参数过多,就会越麻烦。此时apply方法就可以解决。

    apply和call本质相同,但不是一个个传递参数,可以用数组传参且apply会在函数中为你自动展开。

    const arr = ['张三', '李四']
    fn.apply(obj, arr)
    

    除了call和apply可以显示绑定this外,还有bind方法也可以

    bind和call调用方式完全相同,不同的是bind不会立即调用函数,而是返回一个能以后调用的新函数

    const newFn = fn.bind(obj, '张三', '李四')
    newFn()
    

    传入的不是对象:
    如果传入了一个原始值(字符串,布尔类型,数字类型),来当做this的绑定对象,这个原始值转换成它的对象形式。
    如果把null或者undefined作为this的绑定对象传入call/apply/bind,这些值会在调用时被忽略,实际应用的是默认绑定规则

    三 new 绑定

    function fn(a) {
      this.a = a
    }
    const bar = new fn(2)
    console.log(bar.a) // 2
    

    用new操作符创建对象时会发生如下步骤:

    1. 创建一个全新的Object对象实例
    2. 将构造函数的执行对象赋给新生成的这个实例
    3. 这个新对象会绑定到函数调用的this
    4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
      规则:使用构造调用的时候,this会自动绑定在new期间创建的对象上

    所以,new fn(2)就相当于

    const newObj = new Object()
    newObj.a = a
    

    还有一种特殊情况,就是当构造函数通过 "return" 返回的是一个对象的时候,此次运算的最终结果返回的就是这个对象,而不是新创建的对象,因此 this 在这种情况下并没有什么用。注意,返回函数也是返回新的对象,函数对象

    function fn(a) {
      this.a = a
      return {}
    }
    const bar = new fn(2)
    console.log(bar.a) // undefined
    

    四 window 绑定

    fn() {
      console.dir(this.title)
    }
    const obj = {
      title: 'test'
    }
    fn()  // undefined
    

    当点的左侧没有任何东西,也没有通过call、apply、bind方法调用,也没有new关键字,this就会指向了window。

    window.title = '全局'
    fn() {
     console.dir(this.title)
    }
    fn()  // 全局
    

    在ES5添加的严格模式中,this不会指向window对象,而是保持为undefined

    五 四种绑定的优先规则

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

    六 丢失的this

    在某些情况下会丢失 this 的指向,此时,我们就需要借助 call、apply 和 bind 来改变 this 的指向问题。
    示例一,当fn方法作为obj对象的属性调用时,this指向obj对象,当另外一个变量引用fn方法时,因为它作为普通函数调用,所以this指向window对象

    const obj = {
      title: 'test',
      fn: function() {
        console.dir(this.title)
      }
    }
    obj.fn() // test
    const getTitle = obj.fn
    getTitle() // undefined
    

    这种方式实际就是函数调用时,并没有上下文对象,只是对函数的引用,同样的问题,还发生在传入回调函数中

    test(obj.fn)  // 传入函数的引用,调用时也是没有上下文
    

    示例二,即使在函数内部定义的函数,如果它作为普通对象调用,this同样指向window对象

    const obj = {
      title: 'test',
      name: '张三',
      fn: function() {
        console.dir(this.title)
        function getName(){
          console.dir(this.name)
        }
        getName()
      }
    }
    obj.fn() // test
                // undefined
    

    七 练习

    var xuuu = 123
    function  test() {
      var xuuu = 456
      this.aa = 6666    
      return function() {
        console.log(xuuu)
        console.log(this.aa)
        console.log(this.xuuu) 
    }
    
    var sdf=new test()
    sdf()      // 456, undefined, 123
    test()()   // 456,6666,123
    

    结论:第一种情况的中this.aa指向sdf,return中this指向全局对象window;第二种情况中两次this都指向window

    八 箭头函数的this

    在以往的函数中,this 有各种各样的指向(隐式绑定,显示绑定,new 绑定, window 绑定......),虽然灵活方便,但由于不能在定义函数时而直到实际调用时才能知道 this 指向,很容易给开发者带来诸多困扰。比如下面的代码

    function User() {
      this.name = 'John'
    
      setTimeout(function greet() {
        console.log(`Hello, my name is ${this.name}`)  // Hello, my name is 
        console.log(this) // window
      }, 1000)
    }
    
    const user = new User()
    

    如果想把greet里面的this指向user对象该怎么办呢?
    1 使用闭包

    function User(){
      const self = this
      this.name = 'John'
    
      setTimeout(function greet() {
        console.log(`Hello, my name is ${self.name}`)  // Hello, my name is John
        console.log(self) // User {name: "John"}
      }, 1000)
    }
    
    const user = new User()
    

    ** 2 使用显示绑定bind **

    function User() {
      this.name = 'John';
    
      setTimeout(function greet() {
        console.log(`Hello, my name is ${this.name}`); // Hello, my name is John
        console.log(this); // User {name: "John"}
      }.bind(this)(), 1000);
    }
    
    const user = new User();
    

    ** 利用 setTimeout 的可以传更多参数的特性 **

    function User() {
      this.name = 'John';
    
      setTimeout(function greet(self) {
        console.log(`Hello, my name is ${self.name}`); // Hello, my name is
        console.log(self); // window
      }, 1000, this);
    }
    
    const user = new User();
    

    ** 箭头函数如何解决 **

    function User() {
      this.name = 'John';
    
      setTimeout(() => {
        console.log(`Hello, my name is ${this.name}`); // Hello, my name is John
        console.log(this); // User {name: "John"}
      }, 1000);
    }
    
    const user = new User();
    

    箭头函数在自己的作用域内不绑定 this,即没有自己的 this,如果要使用 this ,就会指向定义时所在的作用域的 this 值。在上面的代码中即指向 User 函数的 this,而 User 函数通过 new 绑定,所以 this 实际指向 user 对象

    function foo() {
      return () => {
        console.log(this.a);
      };
    }
    let obj1 = {
      a: 2
    };
    let obj2 = {
      a: 22
    };
    let bar = foo.call(obj1); // foo this指向obj1
    bar.call(obj2); // 输出2 这里执行箭头函数 并试图绑定this指向到obj2 但并不成功
    

    结论:
    1、箭头函数没有自己的this,它的this继承于它外面第一个不是箭头函数的函数的this指向。
    2、箭头函数的 this 一旦绑定了上下文,就不会被任何代码改变
    3、箭头函数使用call、apply、bind时,会自动忽略掉第一个参数
    4、严格模式并不影响箭头函数自己的this

    九 最后的图片

    相关文章

      网友评论

          本文标题:难缠的this

          本文链接:https://www.haomeiwen.com/subject/ylwtoftx.html