美文网首页Vue
vue中使用节流函数踩坑记录

vue中使用节流函数踩坑记录

作者: majunchang | 来源:发表于2020-05-15 11:00 被阅读0次

    前言

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

    举一个🌰 业务场景

    image

    概念:

    关于防抖函数的介绍

    关于addEventListener

    使用示例:

        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中使用?

    首先说一下之前的踩坑行为

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

    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或@注册的监听子组件内触发的事件

    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
    
    
    

    相关文章

      网友评论

        本文标题:vue中使用节流函数踩坑记录

        本文链接:https://www.haomeiwen.com/subject/znoqohtx.html