美文网首页
看一遍就能掌握 js 中的 this 指向

看一遍就能掌握 js 中的 this 指向

作者: Super超_fee3 | 来源:发表于2020-06-01 10:03 被阅读0次
    关于 this 指向,可能有一部分人都是模糊的,本文对常见情况下的 this 指向作出总结,让你不再皱眉。
    

    先了解

    一个基本概念:普通函数的 this 指向不是在定义的时候确定,而是在调用的时候确定。

    两个注意事项:

    • 所有例子在浏览器环境(window 对象同时也是全局对象)运行,而不是 node 环境(global)。
    • 非严格模式指向 Window 的对应严格模式是 undefined。(Window.setTimeout 等除外,由 setTimeout 调用的代码运行在与所在函数完全分离的执行环境上,即使在严格模式下仍然指向 Window)

    接下来从一般形式函数调用、方法调用、apply 和 call 调用、箭头函数、class 等理清指向问题。

    1. 一般形式函数调用

    所谓一般形式函数调用就是 函数名(),this 指向全局对象。

    function test() {
      console.log(this.name) // fang
    }
    var name = 'fang'
    // let、const 声明的变量不是 Window 属性
    const age = 1
    console.log(this) // Window
    console.log(this.age) // undefined
    test()
    

    2. 方法调用

    一个函数被设置为对象(非全局对象)的属性值时,就是方法调用,this 指向对象自身。

    • 当函数属于对象外层属性的属性值

      function test() {
        'use strict'
        console.log(this) // obj
        console.log(this.name) // fang
      }
      var name = 'wang'
      const obj = {
        name: 'fang',
        fun: test
      }
      obj.fun()
      
    • 当函数属于深层对象的属性值,要明确知道它的使用者是谁

      function test() {
        console.log(this) // obj2
        console.log(this.name) // zhang
      }
      var name = 'wang'
      const obj1 = {
        name: 'fang',
        obj2: {
          name: 'zhang',
          fun: test
        }
      }
      
      obj1.obj2.fun()
      
    • 当把对象的属性值函数赋值给一个新的变量,就会变成一般形式函数调用

      function test() {
        console.log(this) // Window
        console.log(this.name) // wang
      }
      var name = 'wang'
      const obj1 = {
        name: 'fang',
        obj2: {
          name: 'zhang',
          fun: test
        }
      }
      var t = obj1.obj2.fun
      t()
      
    • 当属性值函数内部还有函数

      function test() {
        console.log(this) // Window
        console.log(this.name) // wang
      }
      var name = 'wang'
      const obj = {
        name: 'fang',
        foo: function() {
          console.log(this) // obj
          test() // test 是一般形式函数调用
        }
      }
      
      obj.foo() // foo 是方法调用
      

    3. call、apply 和 bind 用来改变 this 指向

    • call: fun.call(thisArg[, arg1[, arg2[, ...]]]),第一个参数是要绑定给 this 的值,后面传入参数列表。

      const obj = {
        name: 'fang'
      }
      function a() {
        console.log(this)
      }
      a.call(obj) // obj
      a.call(null) // 如果第一个参数是 null、undefined,指向 Window(下同)
      
    • apply: fun.apply(thisArg[, [arg1, arg2, ...]]),可接收两个参数,第一个是绑定给 this 的值,第二个是数组。

      const obj = {
        name: 'fang'
      }
      function a() {
        console.log(this)
      }
      a.apply(obj) // obj
      a.apply(undefined) // Window
      
    • bind:fun.bind(thisArg[, arg1[, arg2[, ...]]]),与 call 相似,但是返回的是一个函数,需要手动去执行。

      const obj1 = {
        name: 'fang'
      }
      const obj2 = {
        name: 'wang'
      }
      function a() {
        console.log(this)
      }
      const b = a.bind(obj1)
      const c = a.bind(obj2)
      // 需要手动执行
      b() // obj1
      a() // Window,函数a不会受影响
      c() // obj2
      b.call(null) // obj1(绑定关系一旦确认给新变量,新变量继续使用 call、apply、bind 不会再次改变指向)
      c.bind(obj2)() // obj2
      

    4. 箭头函数

    箭头函数没有自己的 this,看其定义时外层是否有函数,如果有,外层函数的 this 就是内部箭头函数的 this,如果没有,则 this 指向 Window。

    • 箭头函数外层没有函数

      const arrow = () => {
          console.log(this.name) // wang
      }
      var name = 'wang'
      const obj = {
        name: 'fang',
        foo: function() {
          console.log(this) // obj
          arrow() // 看定义时
        }
      }
      obj.foo()
      
        var name = 'wang'
        const obj1 = {
          name: 'fang',
          obj2: {
            name: 'zhang',
            fun: () => {
                console.log(this) // Window
            }
          }
        }
        obj1.obj2.fun()
      
    • 箭头函数外层有函数,注意外层函数的 this 指向按照之前规则判断

      var name = 'wang'
      const obj = {
        name: 'fang',
        foo: function() {
          console.log(this) // obj
          const arrow = () => {
              console.log(this) // 指向外层函数 this
          }
          arrow()
        }
      }
      
      obj.foo()
      
    • 箭头函数能否被改变指向?

      var name = 'wang'
      const obj1 = {name: 'zhang'}
      const obj2 = {
        name: 'fang',
        foo: function() {
          console.log(this) // obj2
          const arrow = () => {
              console.log(this.name) // fang
          }
          arrow.call(obj1) // 箭头函数不会改变 this 指向
        }
      }
      
      obj2.foo()
      

    5. class 类(es6 严格模式)

    • 创建类实例后,再去调用类的方法,this 指向实例对象

        class A {
          constructor({
            age,
            name
          }) {
            this.name = name
            this.age = age
          }
      
          test() {
            console.log(this) // {name:'fang', age:1}
          }
        }
      
        const a = new A({
          age: 1,
          name: 'fang'
        })
        a.test()
      
    • 直接通过 prototype 对象调用 test,指向 prototype

        class A {
          constructor({
            age,
            name
          }) {
            this.name = name
            this.age = age
          }
      
          test() {
            console.log(this)
          }
        }
      
        const a = new A({
          age: 1,
          name: 'fang'
        })
        a.test() // {name:'fang', age:1}
        console.log(A.prototype) // {constructor:f, test:f}
        A.prototype.test() // prototype(想想看是不是可以理解为方法调用)
      
    • 子类创建一个实例后,指向子类实例对象,包括子类调用父类的方法

        class A {
          constructor() {
            this.name = 1
            this.age = 1
            this.sex = 0
          }
            test1(){
                console.log(this)
            }
        }
        class B extends A {
          constructor({name,age}) {
            super() // super 必须置于 this 前
            this.name = name // 如果不写,继承父类的属性 1
            this.age = age // 如果不写,继承父类的属性 1
          }
      
          test2() {
            console.log(this)
          }
        }
        const b = new B({
          name: 3,
          age: 3
        })
        b.test2() // {age:3,name:3,sex:0}
        // 父类的方法被子类调用
        b.test1() // {age:3,name:3,sex:0}
      
    • 子类通过 prototype 调用的指向与父类是有区别的

        class A {
          constructor() {
            this.name = 1
            this.age = 1
            this.sex = 0
          }
          test1(){
              console.log(this)
          }
        }
        class B extends A {
          constructor({name,age}) {
            super() // super 必须置于 this 前
            this.name = name // 如果不写,继承父类的属性 1
            this.age = age // 如果不写,继承父类的属性 1
          }
          test2() {
            console.log(this)
          }
        }
        console.log(B.prototype) // A {constructor: ƒ, test2: ƒ} 注意与父类prototype的区别
        B.prototype.test1() // A {constructor: ƒ, test2: ƒ}
        B.prototype.test2() // A {constructor: ƒ, test2: ƒ}
      

    6. vue 中的 this

    一般来说,在 vue 生命周期函数或自定义方法中 this 指向的是 vue 实例,但是要注意下面的3种情况。

    • 回调函数 then 链式写法用普通函数,this 指向 undefined,可使用 _this=this 获取 vue 实例

      let _this = this // vue实例
      /* eslint-disable */
      request().then( function (res){
        console.log(this) // undefined
        console.log(_this) // vue实例
      })
      
    • setTimeout 执行普通函数指向 Window ,可使用箭头函数获取 vue 实例

      setTimeout(() => {
        console.log(this) // vue 实例
      }, 2000)
      
    • 不应该使用箭头函数来定义 method 函数,箭头函数绑定了父级作用域的上下文,this 指向 undefined。

      methods: {
        todo: () => console.log(this) // undefined
      }
      

    以上理解如果有不对之处请指出。

    相关文章

      网友评论

          本文标题:看一遍就能掌握 js 中的 this 指向

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