JavaScript的节流和防抖
今天一个同学面试被问到一个问题, 谈谈js的函数节流和函数防抖。
懵逼,一脸的懵逼,这可触碰到我的知识盲区了,好像听也没听过这2个东西,痛定思痛,赶紧学习学习。
函数节流: 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
函数防抖: 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
应用场景:
函数节流:当给document加scroll事件时,假定处理函数为fn,那么当滑动鼠标时scroll事件会不断的被触发,影响滑动性能,这时可以用节流处理一下 throttle(fn,time),可以保证fn在time时间内只触发一次
函数防抖:当做随着输入框输入不同内容展示不同的结果列表类似需求时,一般会绑定input的change事件,该事件在用户输入过程中会多次被触发,这时可以用防抖处理一下 debounce(fn,time),可以保证在用户输入完time时间后才触发fn。这里不用 throttle,因为debounce更加符合
例子:
- 节流:实现函数节流我们主要有两种方法:时间戳和定时器
// 时间戳实现方法:
var throttle = function(func, delay) {
var prev = Date.now();
return function() {
var context = this; //this指向window
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
这个节流函数利用时间戳让第一次滚动事件执行一次回调函数,此后每隔1000ms执行一次,在*小于1000ms这段时间内的滚动是不执行的
// 定时器实现方法
var throttle = function(func, delay) {
var timer = null;
return function() {
var context = this;
var args = arguments;
if (!timer) {
timer = setTimeout(function() {
func.apply(context, args);
timer = null;
}, delay);
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
当触发事件的时候,我们设置了一个定时器,在没到1000ms之前这个定时器为null,而到了规定时间执行这个函数并再次把定时器清除。也就是说当第一次触发事件,到达规定时间再执行这个函数,执行之后马上清除定时器,开始新的循环,那么我们看到的效果就是,滚动之后没有马上打印,而是等待1000ms打印,有一个延迟的效果,并且这段时间滚动事件不会执行函数。
单用时间戳或者定时器都有缺陷,我们更希望第一次触发马上执行函数,最后一次触发也可以执行一次事件处理函数
var throttle = function(func, delay) {
var timer = null;
var startTime = Date.now(); //设置开始时间
return function() {
var curTime = Date.now();
var remaining = delay - (curTime - startTime); //剩余时间
var context = this;
var args = arguments;
clearTimeout(timer);
if (remaining <= 0) { // 第一次触发立即执行
func.apply(context, args);
startTime = Date.now();
} else {
timer = setTimeout(func, remaining); //取消当前计数器并计算新的remaining
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
在节流函数内部使用开始时间startTime、当前时间curTime和剩余时间remaining,当剩余时间小于等于0意味着执行处理函数,这样保证第一次就能立即执行函数并且每隔delay时间执行一次;如果还没到时间,就会在remaining之后触发,保证最后一次触发事件也能执行函数,如果在remaining时间内又触发了滚动事件,那么会取消当前的计数器并计算出新的remaing时间。通过时间戳和定时器的方法,我们实现了第一次立即执行,最后一次也执行,规定时间间隔执行的效果,可以灵活运用在开发中
- 防抖:
function debounce(fn, wait) {
var timeout = null; //定义一个定时器
return function() {
if(timeout !== null) {
clearTimeout(timeout); //清除这个定时器
}
timeout = setTimeout(fn, wait);
}
}
// 处理函数
function handle() {
console.log(Math.random());
}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));
- vue中防抖函数的定义和使用方式
/**
* 防抖函数
* @param fn
* @param wait
* @returns {function(...[*]=)}
*/
debounce(fn, wait) {
let _this = this;
return function(...args) {
if(_this.timeout !== null) clearTimeout(_this.timeout); //清除这个定时器
_this.timeout = setTimeout(() => {
fn.apply(_this, args)
}, wait);
}
}
// vue中调用防抖函数
/**
* 滑块值改变事件
* @param val
*/
progressValChange(val) {
// 调用防抖函数
this.debounce(function(time) {
// 播放历史数据
this.historyPlayer(this.historyData,this.speed,time);
},500)(val);
},
如上所见,当持续触发scroll函数,handle函数只会在1秒时间内执行一次,在滚动过程中并没有持续执行,有效减少了性能的损耗
PS:防抖和节流能有效减少浏览器引擎的损耗,防止出现页面堵塞卡顿现象,应该熟练掌握。最后再次感谢原作者的总结,热心分享技术让我们的生活变得更好