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;
首先认识一下,对象的简写。详细说明可以查看: 点击这里 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 () {
}
}
点击这里查看解释 关于 js中 this 指向问题
这里简述一下:
答: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
$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 指向。
var a = function () {
console.log(this)
}
var b = ()=> {
console.log(this)
}
obj = {
name:"123"
}
a.apply(obj) // { name: '123' }
b.apply(obj) // undefined
总结一下,这篇博客的收获。
lodash.debounce
等方法的时候,为保证 this
正确的指向,回调函数推荐使用 匿名函数。this
,导致内部的 this
就是外层代码块的 this
。(正因如此,call,apply,bind 就无法生效了,所以针对 this出现异常的情况,请优先考虑是否是因为箭头函数导致的。)