Vue 中使用 lodash.debounce ,彻底弄懂this指向undefined的原因
start
- 最近在 Vue中使用
lodash.debounce
,传递了箭头函数当做回调函数,却发现 this 指向 undefined。 - 写一篇文章记录一下,问题原因。
解决方案
使用匿名函数的方式,传递回调函数;而不是使用箭头函数。
错误示例:(箭头函数)
<template>
<div>
<input type="text" v-model="num" @input="debounceSayHello" />
</div>
</template>
<script>
import $lodash from "lodash";
export default {
data() {
return {
num: 123,
};
},
methods: {
sayHello() {
console.log("值改变时间", this.num);
},
// 箭头函数的写法
debounceSayHello: $lodash.debounce(() => {
console.log(this); // undefined
// this.sayHello()
}, 500),
},
};
</script>
正确示例(匿名函数):
<template>
<div>
<input type="text" v-model="num" @input="debounceSayHello" />
</div>
</template>
<script>
import $lodash from "lodash";
export default {
data() {
return {
num: 123,
};
},
methods: {
sayHello() {
console.log("值改变时间", this.num);
},
// 匿名函数的写法
debounceSayHello: $lodash.debounce(function() {
console.log(this); // vm实例
this.sayHello()
}, 500),
},
};
</script>
问题原因?
对照我们的示例代码,我们仔细研究一下,出现这个问题原因。
本文源码对标 vue 2.7.14;
2.1 对象的简写形式
首先认识一下,对象的简写。详细说明可以查看: 点击这里 ES6入门-阮一峰-属性的简洁表示法
let a = 1
let obj1 = {
a: a,
b: "lazy",
c: function () {
console.log('tomato1')
}
}
// 等同于
let obj2 = {
a,
b: "lazy",
c() {
console.log('tomato2')
}
}
// 验证
console.log(obj1.a) // 1
console.log(obj2.a) // 1
obj1.c() // tomato1
obj2.c() // tomato2
结论: 所以示例代码中的 methods
其实就是一个对象,sayHello是其中一个属性,该属性的属性值是一个函数。
methods: {
sayHello() {
console.log('值改变时间',this.num)
},
debounceSayHello: $lodash.debounce(function () {
console.log(123)
this.sayHello()
},500)
}
// 上面代码等同于下面的代码
var methods
methods = {
sayHello: function () {
console.log('值改变时间', this.num)
},
debounceSayHello: function () {
}
}
2.2 关于 this 指向的五种情况
这里简述一下:
- 自己调用自己 ,严格模式指向undefined,非严格模式指向window;
- 谁调用这个函数,this指向就指向谁;
- 通过 call,apply,bind。修改this指向;
- new修改this指向;
- 匿名函数;
2.3 为什么可以通过 this.sayHello,调用 methods 中的 sayHello
答:Vue源码中的 initMethods
中有体现。组件实例化的时候,修改 sayHello对应的匿名函数的this,绑定到当前组件实例 vm上。
function initMethods(vm, methods) {
const props = vm.$options.props;
for (const key in methods) {
{
if (typeof methods[key] !== 'function') {
warn$2(`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
`Did you reference the function correctly?`, vm);
}
if (props && hasOwn(props, key)) {
warn$2(`Method "${key}" has already been defined as a prop.`, vm);
}
if (key in vm && isReserved(key)) {
warn$2(`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`);
}
}
// 重点就是下面这句话,绑定methods中的属性,到vm上。
vm[key] = typeof methods[key] !== 'function' ? noop : bind$1(methods[key], vm);
}
}
结论: 我们使用 this.sayHello
调用这个方法。本质上是:调用 this
指向为当前vm
实例的sayHello
2.4 $lodash.debounce中,回调函数的this如何处理?
$lodash.debounce
函数,执行后会返回一个函数。(为了方便说明,我称这个函数为A)
methods: {
debounceSayHello: $lodash.debounce(function () {
console.log(123)
this.sayHello()
},500)
}
// 等同于
methods: {
debounceSayHello: function A(){}
}
当我们调用debounceSayHello,其实就是调用函数A。
函数A中会做一些防抖节流的逻辑。当符合回调函数执行的时候,会触发,我们的回调函数。
它内部为了保证回调函数的this指向正确,使用了apply,处理了回调函数的this。
对应源码如下图:
-
当我们使用箭头函数的时候: 箭头函数无法被 apply 等方法,显式的修改 this 。此时的this指向为外层代码块 (invokeFunc) 的 this,而 invokeFunc 是自己调用自己。 所以此时 func 中 this 指向了 undefined。 (非严格模式下是 window)
-
当我们使用匿名函数的时候:可以被 apply 修改 this,从而指向了函数A的 this (函数A的 this指向 vm)。
所以在使用 lodash.debounce 时,传递的匿名函数,不要使用箭头函数,即可保证正确的 this 指向。
2.5 简化demo说明
var a = function () {
console.log(this)
}
var b = ()=> {
console.log(this)
}
obj = {
name:"123"
}
a.apply(obj) // { name: '123' }
b.apply(obj) // undefined
END
总结一下,这篇博客的收获。
- 在使用
lodash.debounce
等方法的时候,为保证this
正确的指向,回调函数推荐使用 匿名函数。 - 箭头函数根本没有自己的
this
,导致内部的this
就是外层代码块的this
。(正因如此,call,apply,bind 就无法生效了,所以针对 this出现异常的情况,请优先考虑是否是因为箭头函数导致的。)