一個常見的業務場景,咱們要在input搜索框輸入結束後,發送相關請求,獲取搜索數據。頻繁的事件觸發會致使接口請求過於頻繁。因此須要咱們對此加以限制,來禁止沒必要要的請求,以避免資源的浪費~javascript
關於防抖函數的介紹html
使用示例: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)); // 防抖
複製代碼
首先說一下以前的踩坑行爲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> 複製代碼
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.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方法函數
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);
}
複製代碼
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
複製代碼