Class中的this指向

前言

JavaScript中的this指向问题本来是一个入门必会的问题,但是对于classthis的指向问题,发现不少人还有困惑。希望这篇文章能给大家讲清楚。

this的绑定优先级

关于this有不少说法。有的人说this是谁调用就指代谁。有的人说this跟作用域无关,只跟执行上下文有关。两种说法貌似是一个意思。

1.new创建出来的实例去调用方法,this指向当前实例

class Cat {
    jump() {
        console.log('jump',this)
    }
}
const cat = new Cat()
cat.jump() // jump Cat {}
复制代码

2.显式绑定

使用call、apply、bind

function jump() {
  console.log(this.name)
}
const obj = {
  name: '豆芽',
  jump,
}
jump = jump.bind(obj)
jump() // 豆芽
复制代码

3.对象中的方法绑定

function jump() {
  console.log(this.name)
}
const obj = {
  name: '豆芽',
  jump,
}
obj.jump() // 豆芽
复制代码

4.默认绑定

在严格模式下,thisundefined,否则是全局对象。

Class中属性与方法的绑定

class Cat {
  constructor(name) {
    this.name = name
  }
  jump() {
    console.log('jump', this)
  }
  static go() {
    console.log(this)
  }
}
Cat.drink = function() {
  console.log('drink', this)
}
Cat.prototype.eat = function() {
  console.log('eat', this)
}
Cat.prototype.walk = () => {
  console.log('walk', this)
}
let cat = new Cat('豆芽')
复制代码

image.png

通过上图可以看到,Cat所创建出来的实例,其方法挂载在实例的__proto__上面,即挂载在原型对象上。因为cat.proto 与 Cat.prototype指向同一个对象,所以当在cat.__proto__上挂载或者覆盖其原有方法时,所有由Cat所创建出来的实例,都将会共享该方法,所有实例都是通过__proto__属性产生的原型链到原型对象上寻找方法。

但是静态方法不会共享给实例,因为没有挂载在原型对象上面。

而属性是挂载在实例上的,即每一个创建出来的实例,都拥有自己不同值的属性。

Class中this的绑定

当我们打印typeof Cat可知Cat是函数类型,ES6中的class类其实只是个语法糖,皆可以用ES5来实现。由构造函数Cat创建的实例cat是一个对象。在初始化cat实例的时候,在constructor中就会把this上的属性挂载到实例对象上面。

class Cat {
  constructor(name, age) {
    this.name = name
  }
  run() {
    console.log('run', this)
  }
}
let cat = new Cat('豆芽')
cat.name // '豆芽'
cat.run() // run Cat {name: '豆芽'}
复制代码

当调用cat.run()的时候,当前上下文是cat,所以其this指向的是cat这个实例。

class Cat {
  constructor(name) {
    this.name = name
    this.jump = this.jump.bind(this)
    this.drink = () => {
        console.log('drink',this)
    }
  }
  run() {
    console.log('run', this)
  }
  jump() {
    console.log('jump',this)
  }
  static go() { 
    console.log('go',this)
  } 
}
Cat.prototype.walk = () => {
    console.log('walk',this)
}

let cat = new Cat('豆芽')
let run = cat.run
let jump = cat.jump
let go = Cat.go
let walk = cat.walk
let drink = cat.drink

run() // run undefined (严格模式下,this为undefined,可以举一个严格模式下function的例子)
jump() // jump Cat {name: "豆芽", jump: ƒ}
Cat.go() // go class Cat {}
go() // go undefined
cat.walk() // walk Window
walk() // walk Window
cat.drink() // drink Cat {name: "豆芽", jump: ƒ, drink: ƒ}
drink() // drink Cat {name: "豆芽", jump: ƒ, drink: ƒ}
复制代码

解析:

run方法: 当把实例中的方法赋值给一个变量,但是只是赋予了方法的引用,所以当变量在执行方法的时候,其实改变了方法的执行时的上下文。原来执行的上下文是实例cat,后来赋值之后再执行,上下文就变成了全局,this默认绑定。class中使用的是严格模式,在该模式下,全局的this默认绑定的是undefined,不是在严格模式下的时候,若在浏览器中执行,则this默认绑定window

jump方法: 因为在构造函数执行的时候,显式绑定了jump执行的上下文cat实例。所以jump的执行上下文依然是cat实例

go方法: go方法使用静态方法定义,无法共享实例cat,只能在构造函数Cat上直接调用。

walk与drink方法: 这两个方法是用箭头函数定义的。箭头函数的this是在定义函数时绑定的,不是在执行过程中绑定的。简单的说,函数在定义时,this就继承了定义函数的对象。walk是在Cat.prototype.walk定义时的,此时的this指向是window。无论之后赋值给哪个变量,也只是用函数的引用,所以其this还是window。同理,drink在定义的时候,this指向的是该构造函数。

参考文档:es6.ruanyifeng.com/#docs/class

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享