vue中使用节流函数

前言

一个常见的业务场景,咱们要在input搜索框输入结束后,发送相关请求,获取搜索数据。频繁的事件触发会致使接口请求过于频繁。因此须要咱们对此加以限制,来禁止没必要要的请求,以避免资源的浪费~javascript

举一个🌰 业务场景

image

概念:

关于防抖函数的介绍html

关于addEventListenervue

使用示例:java

function debounce(fn) {
        let timeout = null; // 建立一个标记用来存放定时器的返回值
        return function () {
            clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
            timeout = setTimeout(() => {
                // 而后又建立一个新的 setTimeout, 这样就能保证输入字符后的
                // interval 间隔内若是还有字符输入的话,就不会执行 fn 函数
                fn.apply(this, arguments);
            }, 500);
        };
    }
    function sayHi() {
        console.log('防抖成功');
    }

    var inp = document.getElementById('inp');
    inp.addEventListener('input', debounce(sayHi)); // 防抖
复制代码

在vue中使用?

首先说一下以前的踩坑行为git

下面的代码为简易版的一个场景github

function debounce(fn) {
        let timeout = null; // 建立一个标记用来存放定时器的返回值
        return function () {
            clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
            timeout = setTimeout(() => {
                // 而后又建立一个新的 setTimeout, 这样就能保证输入字符后的
                // interval 间隔内若是还有字符输入的话,就不会执行 fn 函数
                fn.apply(this, arguments);
            }, 500);
        };
   }
复制代码

错误的使用方式

<template>
    <div class="search-view"> <div class="header"> <Search class="search-box" v-model='searchValue' @input='getSearchResult' placeholder='搜索想要的好物' /> <span @click="goBack" class="cancel">取消</span> </div> <div class="serach-view-content" /> </div> </template> <script> import Search from './components/Search'; import debounce from './config'; export default { name: 'SearchView', components: { Search }, data() { return { searchValue: '' }; }, methods: { getSearchResult() { debounce(function() { console.log(this.searchValue); })(); } } }; </script> 复制代码

为何错误?

源码层级分析
vue模板编译 的解析事件
export const onRE = /^@|^v-on:/
export const dirRE = /^v-|^@|^:/

function processAttrs (el) {
  const list = el.attrsList
  let i, l, name, value, modifiers
  for (i = 0, l = list.length; i < l; i++) {
    name  = list[i].name
    value = list[i].value
    if (dirRE.test(name)) {
      // 解析修饰符
      modifiers = parseModifiers(name)
      if (modifiers) {
        name = name.replace(modifierRE, '')
      }
      if (onRE.test(name)) { // v-on
        name = name.replace(onRE, '')
        addHandler(el, name, value, modifiers, false, warn)
      }
    }
  }
}
复制代码

总结: 实例初始化阶段调用的初始化事件函数initEvents实际上初始化的是父组件在模板中使用v-on或@注册的监听子组件内触发的事件app

vue的事件机制
Vue.prototype.$on = function(event, fn) {
    const vm = this;
    if (Array.isArray(event)) {
        for (let i = 0; i < event.length; i++) {
            this.$on(event[i], fn);
        }
    } else {
        //这个_events属性就是用来做为当前实例的事件中心,全部绑定在这个实例上的事件都会存储在事件中心_events属性中。
        (vm._events[event] || (vm._events[event] = [])).push(fn);
    }
    return vm;
};

Vue.prototype.$emit = function(event) {
    const vm = this;
    let cbs = vm._events[event];
    if (cbs) {
        cbs = cbs.length > 1 ? toArray(cbs) : cbs;
        let args = toArray(arguments, 1);
        for (let i = 0; i < cbs.length; i++) {
            try {
                cbs[i].apply(vm, args);
            } catch (e) {
                handleError(e, vm, `event handler for "${event}"`);
            }
        }
    }
    return vm;
};
复制代码

vue的initState中 调用了initMethods方法函数

initMethods中挂在methods方法到this上
for (const key in methods) {
        if (process.env.NODE_ENV !== 'production') {
            if (methods[key] == null) {
                warn(
                    `Method "${key}" has an undefined value in the component definition. ` +
                        `Did you reference the function correctly?`,
                    vm
                );
            }
            // 若是和props中某个属性名重名了 抛出异常
            if (props && hasOwn(props, key)) {
                warn(`Method "${key}" has already been defined as a prop.`, vm);
            }
            /* 若是methods中某个方法名若是在实例vm中已经存在而且方法名是以_或$开头的,就抛出异常: 提示用户方法名命名不规范 */
            if (key in vm && isReserved(key)) {
                warn(
                    `Method "${key}" conflicts with an existing Vue instance method. ` +
                        `Avoid defining component methods that start with _ or $.`
                );
            }
            // 将method绑定到实例 vm上 这样咱们就能够经过this.xxx 来访问了
            // 同时若是在vue中 let m1 = this.xxx m1() this也指向vue
            vm[key] = methods[key] == null ? noop : bind(methods[key], vm);
        }

复制代码

划重点:

  • 子组件$emit('input事件')
  • 父组件接收事件
getSearchResult.apply(this, agrs)
<===>  apply的调用能够写成下面的形式
this.getSearchResult(args)

// 进而变成这种执行
debounce(function() {
      console.log(this.searchValue);
})();

// 这里的debounce 返回了一个函数 因而变成
(function (fn) {
      clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
      timeout = setTimeout(() => {
          // 而后又建立一个新的 setTimeout, 这样就能保证输入字符后的
          // interval 间隔内若是还有字符输入的话,就不会执行 fn 函数
          fn.apply(this, arguments);
      }, 500);
})()
// 到这里 其实就变成了匿名函数的自执行
// 因为每次触发input都会返回一个新的匿名函数 生成一个新的函数执行栈 因此防抖失效~

复制代码
那么应该如何调用
<template>
    <div class="search-view"> <div class="header"> <Search class="search-box" v-model='searchValue' @input='getSearchResult()' placeholder='搜索想要的好物' /> <span @click="goBack" class="cancel">取消</span> </div> <div class="serach-view-content"> </div> </div> </template>

<script>
import debounce from 'lodash.debounce';
export default {
    name: 'SearchView',
    components: {
        Search,
    },
    data() {
        return {
            searchValue: '',
        };
    },
    methods: {
        getSearchResult: debounce(function () {
            console.log(this.searchValue);
        }, 500),
    },

};
</script>

复制代码
分析执行过程
getSearchResult().apply(this, args)
<===> 忽略参数行为 只关注执行栈

let func = function () {
    clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
    timeout = setTimeout(() => {
        // 而后又建立一个新的 setTimeout, 这样就能保证输入字符后的
        // interval 间隔内若是还有字符输入的话,就不会执行 fn 函数
        fn.apply(this, arguments);
    }, 500);
};

this.func(args)

<===>
子组件触发input的行为  返回的始终是一个同一个函数体  防抖成功 
类比于文章开始时介绍的addEventListener


复制代码
相关文章
相关标签/搜索