同事分享的防抖节流及在项目上的应用,觉得还可以,就转发过来了~~~
这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战”
防抖节流
什么是防抖节流?
防抖(Debounce):防止重复点击触发事件(最后一个人说了算,在某段时间内,不管你触发了多少次回调,我都只认最后一次)
应用场景:
- 登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖
- 调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖
- 文本编辑器实时保存,当无任何更改操作一秒后进行保存
- ...
代码实现要点:设置一个定时器,通过闭包,抓住定时器变量,控制定时器的添加和删除
//非立即执行版:触发事件后函数不会立即执行,而是在n秒后执行,如果在n秒内又触发了事件,则会重新计算函数执行时间
function debounce(fn, time) {
let timer
return function () {
const args = arguments
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.call(this, args)
}, time)
}
}
// 立即执行版:触发事件后函数会立即执行,n秒内不触发事件才能继续执行函数的效果
function debounce(fn, time) {
let timer;
return function () {
const ctx = this;
const args = arguments;
let callNow = !timer;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
}, time)
if (callNow) fn.call(ctx, args);
};
}
//结合版本
/**
* @desc 函数防抖
* @param fn 函数
* @param wait 延迟执行毫秒数
* @param immediate true 表立即执行,false 表非立即执行
*/
function debounce (fn, wait, immediate) {
let timer
return function () {
const ctx = this
const args = arguments
if (timer) clearTimeout(timer)
// 立即执行
if (immediate) {
const callNow = !timer
timer = setTimeout(() => {
timer = null
}, wait)
if (callNow) fn.call(ctx, args)
} else {
timer = setTimeout(() => {
fn.call(ctx, args)
}, wait)
}
}
}
节流(Throttle):指定时间间隔内只会执行一次任务(在某段时间内,不管你触发了多少次回调,我都只认第一次,并在计时结束时给予响应)
代码实现要点:主要分定时器版和时间戳版
场景:
- scroll 事件,每隔一秒计算一次位置信息等
- 浏览器播放事件,每隔一秒计算一次进度信息等
- input 框实时搜索并发送请求展示下拉列表,没隔一秒发送一次请求 (也可做防抖)
//定时器版:函数不会立即执行,并且每x秒执行一次,在停止触发事件后,函数还会再执行一次
function throttle(fn, time) {
let timeout;
return function () {
const args = arguments
if (!timeout) {
timeout = setTimeout(() => {
fn.call(this, args)
timeout = null
}, time);
}
}
}
//时间戳版: 在持续触发事件的过程中,函数会立即执行,并且每x秒执行一次
function throttle(fn, time) {
let previous = 0;
return function() {
let now = Date.now();
let context = this;
let args = arguments;
if (now - previous > time) {
fn.apply(context, args);
previous = now;
}
}
}
//结合版
/**
* @desc 函数节流
* @param func 函数
* @param wait 延迟执行毫秒数
* @param type 1 表时间戳版,2 表定时器版
*/
function throttle(fn, time ,type) {
if(type===1){
let previous = 0;
} else if(type===2){
let timeout;
}
return function() {
let context = this;
let args = arguments;
if(type===1){
let now = Date.now()
if (now - previous > time) {
fn.apply(context, args);
previous = now;
}
}else if(type===2){
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
fn.apply(context, args)
}, time)
}
}
}
}
-
在vue项目中的使用方式
自定义指令方式
//结合第三方库Lodash
/**
* 防抖节流
* 1.根据修饰符判断是防抖还是节流
* 使用方法如下
* (1)v-debounceOrThrottle.debounce 防抖
* (2)v-debounceOrThrottle.throttle 节流
* 2.根据传入参数wait、leading、trailing 决定 wait 前后如何触发(可不传使用默认值)
* 使用方法如下
* (1)v-debounceOrThrottle:{wait: 2000,leading:false,trailing:true}.debounce
* @params wait 延迟时间
* @params leading:true(false) 指定调用在[防抖/节流]开始之前 (后)
* @params trailing: true(false) 指定调用在[防抖/节流]开始之后(前)
*/
import _debounce from 'lodash/debounce'
import _throttle from 'lodash/throttle'
let fn = null
export default {
inserted: function(el, binding) {
const { modifiers, arg } = binding
const time = 2000
let _arg = {}
if (arg) _arg = eval('(' + arg + ')')
const { wait = time, leading = true, trailing = false } = _arg
if (modifiers.throttle) {
// 节流
fn = _throttle(binding.value, wait, {
leading,
trailing
})
} else {
// 防抖
fn = _debounce(binding.value, wait, {
leading,
trailing
})
}
el.addEventListener('click', fn)
},
//unbind: 只调用一次,指令与元素解绑时调用
unbind: function(el) {
fn && el.removeEventListener('click', fn)
}
}