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

关于 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
    }
    

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。