最近碰到了一个问题,环境是vue2、ts、vue-class-component,写一个方法时发现声明的变量虽可以访问到但没有响应式,随后发现方法中的this不是VueComponent,在检查为什么this指向有问题时发现新写的方法用的是箭头函数,改成普通函数后就正常了,对于这块有点疑惑,因此整理记录关于this指向的情况。
1.不同情况下this的指向
方法/函数调用中的this
window.name = 'win'
function fun () {
console.log(this.name)
}
let obj1 = {
name: 'jack',
fun
}
let obj2 = {
name: 'bob',
}
obj2.fun = obj1.fun
fun() // win
obj1.fun() // jack
obj2.fun() // bob如上代码,直接执行fun函数,打印“win”,表明此时函数中的this指向的对象是window。而使用obj1.fun()调用fun函数,打印“jack”,此时this指向的对象是obj1。obj2.fun()的执行结果为“bob”,表明obj2对象的fun函数虽然是由obj1的fun函数赋值得到,但由于是obj2对象调用的,所以this指向obj2,打印了obj2的name属性。
window.name = 'win'
let fun = () => {
console.log(this.name)
}
let obj = {
name: 'jack',
funA
}
obj.fun() // win如上代码,当fun函数为箭头函数时,因为定义funB时所在的作用域是在全局作用域window下,所以funB内的this是window,因此obj.fun()的打印结果是”win“而非”jack“。
事件回调中的this
行内绑定
<input type="button" value="按钮" onclick="handleClick()">
<script>
function handleClick(){
console.log(this) // Window {}
}
</script>直接将函数执行语句"handleClick()"作为属性绑定到dom标签上,当click事件触发时,语句可视为是在全局作用域下执行的,因此this指向window。
<input type="button" value="按钮" onclick="handleClick()">
<script>
let handleClick = () => {
console.log(this) // Window {}
}
</script>当回调函数是箭头函数时,函数内this也为window。
js动态绑定
<input type="button" value="按钮" id="btn">
<script>
btn.onclick = function() {
console.log(this) // input
}
</script>通过js赋值函数给dom对象的onclick属性的方式绑定回调函数,回调函数是被dom对象所调用的,因此this指向dom对象。
<input type="button" value="按钮" id="btn">
<script>
btn.onclick = function() {
console.log(this) // Window {}
}
</script>当绑定的回调函数是箭头函数时,函数内this指向window。
事件监听
<input type="button" value="按钮" id="btn">
<script>
btn.addEventListener('click', function() {
console.log(this)
})
</script>通过事件监听方法绑定的回调函数,this同样指向了dom对象。
<input type="button" value="按钮" id="btn">
<script>
btn.addEventListener('click', () => {
console.log(this)
})
</script>当绑定的回调函数是箭头函数时,函数内this指向window。
构造函数中的this
function Obj() {
console.log(this)
}
let o = new Obj() // Obj {}
console.log(this) // Window {}如上代码第五行执行new Obj()语句时,打印了“Obj {}”,这是因为在new关键字会首先创建一个空对象,并将控对象的隐式原型指向构造函数的显式原型,最后将构造函数中的this指向了这个新创建的对象,因此在构造函数中,this会指向一个新创建的对象,同时它也是new语句的返回值。
另外箭头函数由于没有自己的this,是不可以作为构造函数的。
call、apply、bind
call、apply和bind是js中三个很神奇的函数,使用它们可以改变函数体内部this的指向。
let func = function() {
console.log(this.name, [...arguments].join(','))
}
let obj1 = {
name: 'obj1',
func
}
let obj2 = {
name: 'obj2',
}
obj1.func(0) // obj1 0
obj1.func.call(obj2, 1, 2, 3) // obj2 1,2,3
obj1.func.apply(obj2, [4, 5, 6]) // obj2 4,5,6如上代码,call和apply方法都可以改变函数内部this的指向,区别在于call方法第一个参数是所要改变的this(上下文),其它参数是func函数的参数,apply方法的第二个参数是fun函数参数的数组。
let func = function() {
console.log(this.name, [...arguments].join(','))
}
let obj1 = {
name: 'obj1',
func
}
let obj2 = {
name: 'obj2',
}
let newFunc = obj1.func.bind(obj2)
newFunc(1, 2, 3) // obj2 1,2,3bind方法可以返回一个新的函数,这个新函数的this为bind的参数,这样无论新函数在哪里被调用,其内部的this都是不会变的。
class中this的指向
class Person {
constructor(name){
this.name = name
}
getName() {
console.log(this.name)
}
}
let p = new Person("tom")
p.getName() // tomclass中所定义的普通函数会被添加到类的原型上,调用方法时,调用方是类的实例对象,因此方法中的this指向类的实例对象。
class Person {
constructor(name){
this.name = name
}
getName = () => {
console.log(this.name)
}
}
let p = new Person("tom")
p.getName() // tomclass中所定义的箭头函数会在constructor中自动添加到类的实例对象上,调用方法时,箭头函数的this也是指向类的实例对象的。
箭头函数
箭头函数体内的this对象,是定义该函数时所在的作用域所指向的对象,而不是使用时所在作用域指向的对象。
window.name = 'win'
let funA = function() {
console.log(this.name)
}
let funB = () => {
console.log(this.name)
}
let obj1 = {
name: 'jack',
funA
}
let obj2 = {
name: 'bob',
funB
}
obj1.funA() // jack
obj2.funB() // win如上代码,obj2.funA()和obj2.funB()打印结果分别是“jack”和“win”,说明箭头函数funB内的this取的是window,因为定义funB时所在的作用域是在全局作用域下,所以funB内的this是window。
2.问题分析
将开篇所提到的问题进行简化:
<template>
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
@Component({})
export default class basicInfoTab extends Vue {
public name = 'aha'
public click() {
console.log(this);
console.log(this.name);
}
public click1 = () => {
console.log(this);
console.log(this.name);
}
created() {
this.click() // VueComponent{...}, aha
this.click1() // basicInfoTab{...}, aha
}
}
</script>
<style scoped lang="scss">
</style>如上的Vue代码中,在组件成功挂载后的created回调函数中,分别调用了普通函数click和箭头函数click1打印this和this.name,他们所打印出来的this.name是一样的,但普通函数中的this是Vue组件的实例对象,而箭头函数中的this是basicInfoTab类的实例对象。
按照上面class中this的指向所述的那样,普通函数和箭头函数中的this应该是一样的才对。要搞清楚原因就需要去探究basicInfoTab类成为组件类,以及实例化组件类的过程,通过查询资料了解到大概有以下步骤:
- 实例化类:
Vue使用“new”关键字和类的构造函数创建类的新实例。 - 初始化组件:
Vue初始化新创建的类实例,设置其数据(data)、计算属性(computed)、方法(methods)等。 - 创建组件实例:
Vue将初始化后的类实例转化为Vue组件实例。在这个过程中,Vue会添加一些特殊的属性和方法,例如生命周期钩子(如created、mounted等)。 - 挂载组件:最后,
Vue将组件实例挂载到DOM元素上。在这个过程中,Vue会创建一个新的Vue实例,并将模板编译成虚拟DOM,然后将虚拟DOM渲染成真实DOM,并插入到挂载的元素中。
由上可知,VueComponent组件实例对象是在basicInfoTab类实例对象的基础上初始化的,click是普通函数,因此他的this是VueComponent组件实例对象,由于click1是箭头函数,所以click1内部this会一直指向basicInfoTab类的实例对象。即使是VueComponent组件实例对象调用了click1,其内部this仍然还是指向basicInfoTab类实例对象。
将此情况换成一个更简单的例子:
class Person {
a() {
console.log(this)
}
b = () => {
console.log(this)
}
}
let p = new Person()
class Dog {
a = p.a
b = p.b
}
let d = new Dog()
d.a() // Dog {...}
d.b() // Person {...}上面代码中,Dog类中的属性a和b都是从Person类的实例对象p赋值而来的,Dog类的实例对象d分别调用a方法和b方法,作为普通函数的a方法中this指向自身,而箭头函数根据其定义依然指向着Person类实例对象p。
因此可以对开篇遇到的问题进行解释,因为箭头函数中的this是basicInfoTab类的实例对象,所读取到的属性没有经过Vue的处理,所以不具备响应式。
3.总结
- 方法调用模式下,
this总是指向调用的对象,this的指向与所在方法的调用位置有关,而与方法的声明位置无关(箭头函数例外); - 在事件回调中,行内绑定的事件不论是普通函数还是箭头函数,它们的
this都指向window;动态绑定及事件监听方式绑定的普通函数,this指向dom对象,绑定的箭头函数则指向window; - 构造函数调用模式下,
this指向被构造的对象; apply、call、bind调用模式下,this指向函数的第一个参数;class中,不论是普通函数还是箭头函数内的this都指向实例对象;- 箭头函数体内的
this和实际调用函数的对象无关,是定义该函数时所在的作用域所指向的对象,而不是使用时所在作用域指向的对象。
参考内容:
https://zhuanlan.zhihu.com/p/42145138
https://juejin.cn/post/6973654160872833038
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this#%E5%B0%9D%E8%AF%95%E4%B8%80%E4%B8%8B
https://juejin.cn/post/6981251280236707853#heading-3
https://juejin.cn/post/7035257186565488670
https://juejin.cn/post/6954692013489029151#heading-7
https://juejin.cn/post/6930896449878097927#heading-14
https://www.zhihu.com/tardis/zm/art/57204184?source_id=1003
https://blog.csdn.net/yiyueqinghui/article/details/106792408
https://juejin.cn/post/6844903906917482503


Comments | NOTHING