1.节流函数
听名字我们也大概知晓什么意思,意思是创建并返回一个像节流阀一样的函数,当重复调用函数的时候,最多每隔 wait毫秒调用一次该函数
听了上面的解析,这里就有一个问题,规定时间内执行,到底是++最开始就执行一次++,间隔wait秒在执行,还是说++wait秒中之后执行++,再wait秒中之后执行,网上看了很多博客,都没有提到这一点,其实这两种应用的是不一样的场景,下面会说到。
1.1在函数调用前先执行
思路如下:
利用闭包,创建一个变量,用来保存上次执行的时间,然后在函数中判断,如果时间间隔大于wait秒,执行函数,否则不作为
/**
* @param func 执行函数
* @param wait 时间间隔
* @returns {Function}
*/
let throttle = function (func, delay) {
let prev = 0;
return function () {
let context = this;
let args = arguments;
let now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
适用场景:防止用户重复提交,如表单提交,发送短信,输入次数过多等
1.2 在时间间隔之后执行方法
思路如下:
使用定时器,如果定时器不存在,则创建定时器,定时器中执行方法,同时清除定时器id
let throttle = function (func, delay) {
let timer = null;
return function () {
let context = this;
let args = arguments;
if (!timer) {
timer = setTimeout(function () {
func.apply(context, args);
timer = null;
}, delay);
}
}
}
适用场景:滚动条事件,搜索框输入发送ajax,输入框判断是否合法等
1.3让函数前时间间隔前后都执行
采用时间戳加定时器的形式
let throttle = function (func, delay) {
let timer = null;
let startTime = Date.now(); //设置开始时间
return function () {
let curTime = Date.now();
let remaining = delay - (curTime - startTime); //剩余时间
let context = this;
let args = arguments;
if (remaining <= 0) { // 间隔大于或者等于delay秒
func.apply(context, args);
startTime = Date.now();
} else { // 间隔小于或者等于delay秒
clearTimeout(timer);
timer = setTimeout(func, remaining); //取消当前计数器并计算新的remaining
}
}
}
remaining为两次方法执行间隔与delay的差距,为负数则表示,间隔时间大于delay秒,反之小于delay秒
1.4 扩展,结合上面三个方法,动态使用
使用第三个参数options,以对象形式,可以传入两个参数leading,trailing
/**
* 创建并返回一个像节流阀一样的函数,当重复调用函数的时候,最多每隔 wait毫秒调用一次该函数
* @param func 执行函数
* @param wait 时间间隔
* @param options 如果你想禁用第一次首先执行的话,传递{leading: false},
* 如果你想禁用最后一次执行的话,传递{trailing: false}
* @returns {Function}
*/
let throttle = function (func, wait, options) {
let context, args, result; //this上下文,参数,返回值
let timeout = null; //定时器id
let previous = 0; //上次执行的时间
if (!options) options = {}; //禁用首次或者末次执行
let later = function () { //remaining秒之后执行方法
//初始化函数
previous = options.leading === false ? 0 : new Date().getTime(); //没有禁用第一次执行,则previous=0
timeout = null; //初始化定时器id
result = func.apply(context, args); //执行方法
context = args = null;
};
return function () {
let now = new Date().getTime(); //获取当前时间
if (!previous && options.leading === false) {
//当第一次执行时,如果禁用第一次执行(options.leading),将本次执行时间赋值给上次执行
previous = now
}
let remaining = wait - (now - previous); //用时间间隔wait减去(本次运行的时间now-上次运行的时间previous),使时间间隔为wait
context = this; //获取当前环境this
args = arguments; //获取函数参数
if (remaining <= 0 || remaining > wait) { //如果两次方法执行时间间隔刚好为wait,或者大于wait,
previous = now; //将本次时间赋值给上次
result = func.apply(context, args); //执行方法
context = args = null
} else if (!timeout && options.trailing !== false) {
// 时间间隔小于wait,且定时器不存在,且最后一次执行
timeout = setTimeout(later, remaining); //在remaining时间后执行方法,remaining为距离wait所剩余的时间
}
return result;
};
}
大致原理就是根据变量初始化previous变量,和是否生成定时器
2.防抖函数
当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
这个和节流不同的地方是,这个函数如果一直触发,则一直不会执行,只会在不触发的wait秒后执行一次
在函数停止执行的wait秒后执行
let debounce = function (fn, wait) {
let timeout = null;
return function () {
if (timeout !== null) clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
原理很简单,使用定时器,每次运行时判断定时器是否存在,存在则清除定时器,在生成一个wait秒后执行的定时器
扩展,自定义函数首次执行还是末次执行
和上面一样,传入第三个参数,控制执行
/**
* 防反跳。func函数在最后一次调用时刻的wait毫秒之后执行!
* @param func 执行函数
* @param wait 时间间隔
* @param options 如果你想第一次首先执行的话,传递{leading: true},
* 如果你想最后一次执行的话,传递{trailing: true}
* @returns {Function}
*/
let debounce = function (func, wait, options) {
let timeout, args, context, timestamp, result;
let later = function () {
let last = new Date().getTime() - timestamp; // timestamp会实时更新
if (last < wait && last >= 0) { //如果定时器执行时间距离上次函数调用时间,大于0,小于wait,则重新生成定时器,时间间隔为距离wait所剩余的时间
timeout = setTimeout(later, wait - last);
} else {
timeout = null; //清除定时器
if (options.trailing) { //如果定义时间间隔后执行
result = func.apply(context, args);
if (!timeout) context = args = null;
}
}
};
return function () {
context = this; //绑定函数上下文
args = arguments;
timestamp = new Date().getTime(); //当前执行方法时间
let callNow = options.leading && !timeout;
if (!timeout) {
//生成定时器
timeout = setTimeout(later, wait);
}
if (callNow) { //如果定义首次执行
//立即执行
result = func.apply(context, args);
context = args = null;
}
return result;
};
}
原理,执行时判断定时器,如果定时器存在,则不作为,然后在定时器执行方法中判断时间间隔,大于0小于wait,则生成新的定时器,每次执行方法动态更新timestamp的值。
本文首发于 https://zhtblog.cn/