您的位置:首页 > Web前端 > Vue.js

vue使用节流函数的踩坑实例指南

2021-05-25 04:10 941 查看

前言

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

举一个🌰 业务场景

概念:

关于防抖函数的介绍

关于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使用节流函数踩坑的文章就介绍到这了,更多相关vue节流函数踩坑内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  vue 节流 函数