最近在学 react 的时候,因为 react 都是用 class 来构建组件,对 class 中 this 的指向有一点迷惑。现在做些总结。
this 的绑定优先级
this 的绑定一共有四种弄方式,优先级逐级递减。
this 的本质就是:this 跟作用域无关的,只跟执行上下文有关。
1、new 创建出来的实例去调用,this 指向当前实例
eg.
// 实例上的绑定
let cat = new Cat()
cat.speak()
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)
}
let obj = {
name: '米粒',
jump
}
obj.jump() // 米粒
4、默认绑定
在严格模式下,this 是 undefined,否则是全局对象
Class 中属性与方法的绑定
class Cat {constructor(name, age) {
// name 做绑定 age 未绑定
this.name = name
}
jump() {console.log('jump',this)
}
// 可以直接调用 静态方法通常用于为一个应用程序创建工具函数。static go() {console.log(this)
}
}
// 可以直接调用 与使用 static 差不多
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 中的米粒', '5 个月')
打印出 cat 与 Cat 的原型对象
可以看到,Cat 所创建出来的实例,其方法挂载在实例的__proto__上面,即 挂载在原型对象 上。因为 cat.__proto__与 Cat.prototype 指向同一个对象,所以当在 cat.__proto__上挂载或者覆盖其原有方法时,所有由 Cat 所创建出来的实例, 都将会共享该方法,所有实例都是通过__proto__属性产生的原型链到原型对象上寻找方法。
但是静态方法不会共享给实例,因为没有挂载在原型对象上面。
而属性是挂载在实例上的,即每一个创建出来的实例,都拥有自己的不同值的属性。
class 中 this 的绑定
前景提要:当我们打印 typeof Cat 可知是函数类型,ES6 中的 class 类其实只是个语法糖,皆可以用 ES5 来实现。由构造函数 Cat 创建的实例 cat 是一个对象。在初始化 cat 实例的时候,在 constructor 中就会把 this 上的属性挂载到实例对象上面。
另外牢记,this 的指向与作用域无关,与调用执行函数时的上下文相关。
示例一:
class Cat {constructor(name, age) {this.name = name}
run() {console.log('run',this)
}
}
let cat = new Cat('米粒', '5 个月')
cat.name // '米粒'
cat.run() // run Cat {name: "米粒"}
当调用 cat.run()的时候,当前上下文是 cat, 所以其 this 指向的是 cat 这个实例。
示例二:
class Cat {constructor(name, age) {
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('米粒', '5 个月')
let run = cat.run
let jump = cat.jump
let go = Cat.go
let walk = cat.walk
let drink = cat.drink
run() // run undefined
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 实例。由文章开头 this 绑定的优先级可知,显式绑定 > 默认绑定。所以 jump 的执行上下文依然是 cat 实例
go 方法:go 方法使用静态方法定义,无法共享个实例 cat,只能在构造函数 Cat 上直接调用。
walk 与 drink 方法:这两个方法是用箭头函数定义的。箭头函数的 this 是在定义函数时绑定的,不是在执行过程中绑定的。简单的说,函数在定义时,this 就继承了定义函数的对象。
所以 walk 是在 Cat.prototype.walk 时定义的,此时的 this 指向是 window。无论之后赋值给哪个变量,也只是函数的引用,所以其 this 还是 window。
同理,drink 在定义的时候,this 指向的是该构造函数。