最近碰到了一个问题,环境是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,3
bind
方法可以返回一个新的函数,这个新函数的this
为bind
的参数,这样无论新函数在哪里被调用,其内部的this
都是不会变的。
class中this的指向
class Person {
constructor(name){
this.name = name
}
getName() {
console.log(this.name)
}
}
let p = new Person("tom")
p.getName() // tom
class
中所定义的普通函数会被添加到类的原型上,调用方法时,调用方是类的实例对象,因此方法中的this
指向类的实例对象。
class Person {
constructor(name){
this.name = name
}
getName = () => {
console.log(this.name)
}
}
let p = new Person("tom")
p.getName() // tom
class
中所定义的箭头函数会在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