前言
JavaScript
中的this
指向问题本来是一个入门必会的问题,但是对于class
中this
的指向问题,发现不少人还有困惑。希望这篇文章能给大家讲清楚。
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.默认绑定
在严格模式下,this
是undefined
,否则是全局对象。
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('豆芽')
复制代码
通过上图可以看到,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
指向的是该构造函数。