Vue 中使用 lodash.debounce ,彻底弄懂this指向undefined的原因

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 指向的五种情况

点击这里查看解释 关于 js中 this 指向问题

这里简述一下:

  1. 自己调用自己 ,严格模式指向undefined,非严格模式指向window;
  2. 谁调用这个函数,this指向就指向谁;
  3. 通过 call,apply,bind。修改this指向;
  4. new修改this指向;
  5. 匿名函数;

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。

对应源码如下图:

Vue 中使用 lodash.debounce ,彻底弄懂this指向undefined的原因_第1张图片

  1. 当我们使用箭头函数的时候: 箭头函数无法被 apply 等方法,显式的修改 this 。此时的this指向为外层代码块 (invokeFunc) 的 this,而 invokeFunc 是自己调用自己。 所以此时 func 中 this 指向了 undefined。 (非严格模式下是 window)

  2. 当我们使用匿名函数的时候:可以被 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

总结一下,这篇博客的收获。

  1. 在使用 lodash.debounce 等方法的时候,为保证 this 正确的指向,回调函数推荐使用 匿名函数
  2. 箭头函数根本没有自己的 this,导致内部的 this 就是外层代码块的 this。(正因如此,call,apply,bind 就无法生效了,所以针对 this出现异常的情况,请优先考虑是否是因为箭头函数导致的。

你可能感兴趣的:(vue,vue.js,javascript,前端)