看一遍就能掌握 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
    }
    

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352