一、在前端开发当中,有些交互事件,会频繁触发。这样会导致我们的页面渲染性能,如果频繁触发接口调用的话,会直接导致服务器性能的浪费。
例如:键盘事件 keyup作为测试
-
未做处理:
函数被调用:0次
const count = document.getElementById('count');
const pt = document.getElementById('ipt');
let init = 0
pt.onkeyup = function() {
count.innerText = ++init
}
运行效果:
每次输入都会触发事件的执行。如果我们用这样的方式去检测:当前用户输入的用户名是否可用?如此高频率的触发不仅是极大的浪费,而且用户还没有输入完成就检测,对用户的提示也不好。应该等用户输入完了,我们再去触发函数,下面我们优化一下:
-
未做处理:
函数被调用:0次
-
防抖处理:
函数被调用:0次
const Count2 = document.getElementById('count2');
const pt2 = document.getElementById('pt2');
// 设置一个默认值 500ms
const debounce = (fn, wait = 500) => {
let time = null
return function(arguments) {
const _this = this, args = arguments
clearTimeout(time)
time = setTimeout(() => {
fn.apply(_this, [args])
}, wait)
}}
let init2 = pt2.onkeyup = debounce(function() {
Count2.innerText = ++init2
运行效果:
可以看到,加了防抖函数之后,当我们在频繁输入的时候,函数并没有执行, 只有在函数指定的间隔内(500ms)不再输入了,才会执行函数。如果在时间间隔之内继续输入,会触发函数重新计数。
函数防抖:在事件触发后的n秒之后,再去执行真正需要执行的函数,如果在这n秒之内事件又被触发,则重新开始计时。
也就是说,如果用户在间隔时间内一直触发函数,那么这个防抖函数内部的真正需要执行的函数将永远无法执行。
那有没有什么好点的办法,让用户在输入过程中,既能触发真实需要的函数,又能达到优化的效果?
答案是肯定的,那就是:
函数截流:规定好一个单位时间,触发函数一次。如果在这个单位时间内触发多次函数的话,只有一次是可被执行的。想执行多次的话,只能等到下一个周期里。
截流处理:
函数被调用:0次
当前时间(分/秒):
const count3 = document.getElementById('count3');
const pt3 = document.getElementById('pt3');
const time = document.getElementById('time');
const throttle = (fn, hold = 1000) => {
let last, deferTimer
return function(arguments) {
const _this = this, args = arguments
let now = +new Date()
if(last && now < last + hold) {
clearTimeout(deferTimer)
deferTimer = setTimeout(function () {
last = now
fn.call(_this, args)
}, hold)
} else {
last = now
fn.call(_this, args)
}
}}
let init3 = 0
const Ipt = throttle(function() {
let time = new Date().getMinutes() + ':' + new Date().getSeconds()
time.innerText = time
count3.innerText = ++init3
}, 1000) // 初始化一下
oIpt3.onkeyup = function() {
Ipt()
}
二、应用场景:防抖和截流都是用来防止高频率的js代码的执行
1、防抖:防抖本质上就是以最后的操作为标准
例如:此时此刻我们都在排队等公交,司机说必须等到坐满才会发车,这时候
的参照标准就是最后一个人上车,公交车好比我们的js代码,最后一个人就充当我们的
执行条件。例如:
let setTimer;
let shake = function() {
clearTimeout(setTimer);
setTimer = setTimeout(() => {
console.log("这里是实际的业务代码");
}, 0);
};
let interTimer = setInterval(() => {
shake();
}, 0);
let timer = setTimeout(() => {
clearInterval(interTimer);
clearTimeout(timer);
timer = null;
interTimer = null;
}, 2000);
执行以上代码,控制台会在 2s 后打出日志,2s 之内的操作都被清空,以最后一次的操作为准。
如果监听滚动事件,假设两秒以内用户在不断的平凡的触发onScroll事件,只有用户暂停滚动后,才会去执行响应的操作,代码如下:
// 函数防抖
var timer = false;
document.getElementById("xxxx").onscroll = function(){
clearTimeout(timer); // 清除未执行的代码,重置回初始化状态
timer = setTimeout(function(){
console.log("函数防抖");
}, 300);
};
2、截流:
(1)定时器实现截流
let isAllow = true;
function shake() {
let fun = function() {
if (!isAllow) return;
isAllow = false;
let timer = setTimeout(() => {
console.log("这里是实际的业务代码");
clearTimeout(timer);
timer = null;
isAllow = true;
}, 1000);
};
fun();
}
let interTimer = setInterval(() => {
shake();
}, 0);
执行以上会看到控制台每隔 1s 后打印出结果,1s 内不会执行打印日志
(2)闭包实现函数截流:
// fn是我们需要包装的事件回调, interval是时间间隔的阈值
function fun(fn, interval) {
let last = 0; // last为上一次触发回调的时间
// 将throttle处理结果当作函数返回
return function() {
let context = this; // 保留调用时的this上下文
let args = arguments; // 保留调用时传入的参数
let now = +new Date(); // 记录本次触发回调的时间
// 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
if (now - last >= interval) {
// 如果时间间隔大于我们设定的时间间隔阈值,则执行回调
last = now;
fn.apply(context, args);
}
};
}
// 用fun来包装scroll的回调
const better_scroll = fun(() => console.log("触发了滚动事件"), 1000);
setInterval(() => better_scroll(), 0);