防抖&节流是JS中常见的性能优化手段,当我们在执行一些事件,如浏览器的resize,scroll;鼠标的mousemove,mouseover;input输入框的keypress时,如果我们一直不断的调用绑定在事件上的callback函数,就会极大地浪费性能,严重时会导致页面的卡顿.所以为了优化性能,我们需要对这类事件进行调用次数的限制。
防抖:触发事件后的一段时间后才执行函数,如果在这段时间内被再次触发,则会重新计时
手写防抖第一版
根据定义,我们先使用setTimeout和clearTimeout创建一个基本的防抖函数,根据MDN,调用setTimeout会返回一个timeoutID,我们可以通过clearTimeout清除指定ID的定时器
let timer = null;
function debounce(fn,delay = 1000) {
clearTimeout(timer);
timer = setTimeout(function () {
fn();
},delay);
}
//test
function testDebounce() {
console.log('test');
}
document.onmousemove = function () {
debounce(testDebounce);
}
1.clearTimeout的作用是什么? 清除之前已经存在的debounce,如果不清除则会重复调用之前的debounce,做不到防抖 2.执行多个debounce会有什么问题? 设置多个debounce会导致需要设置多个timer,函数应该实现单一功能原则,保证其自身的高内聚低耦合,所以这个函数设计是不合理的
手写防抖第二版
1.如果要让内部函数可以使用外层函数作用域中的变量,可以使用闭包; 2.arguments指向传入的参数; 3.apply会将调用的函数绑定到apply第一个参数指向的上下文中,并且可以接受一个数组(或类数组对象)作为后续的参数
function debounce(fn,delay = 1000) {
let timer = null;
return function() {
let _this = this;
args = arguments;
if(timer) clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(_this,args);
},delay);
};
}
1.为什么设置_this? 2.为什么要将apply设置到新写的_this上? 在apply中,会将调用的函数绑定到对应的this里,所以如果不使用先前创建的_this,而是直接使用this,(比如调用者此时为input,如果使用this,因为fn是在全局中调用的,则会将调用者指向window,调用apply会将当前的上下文显示的绑定在this上)
function debounce(fn,delay = 1000) {
let timer = null;
return function() {
let _this = this;
args = arguments;
if(timer) clearTimeout(timer);
timer = setTimeout(function () {
fn(args);//调用fn的是window,而不是我们所预期的对象,如input
fn.apply(_this,args);//显示地将this指向调用其的函数
},delay);
};
}
// test
function testDebounce(e,content) {
console.log(e,content);
}
var testDebounceFn = debounce(testDebounce);
document.onmousemove = function(e) {
testDebounceFn(e,'debounce');
}
手写防抖第三版
1.使用箭头函数,将会指向函数定义时的this; 2.箭头函数可以通过扩展运算符避免定义arguments;
function debounce(fn,delay = 1000) {
let timer = null;
return(...args) => {
if(timer) clearTimeout(timer);
timer = setTimeout(() => {
fn(args);
},delay);
};
}
节流:每隔一段时间内,只执行一次函数
手写节流第一版
根据定义,在timer存在的时候直接return,不存在的时候执行setTimeout
function throttle(fn,delay = 1000) {
let timer = null;
return function () {
let _this = this;
let args = arguments;
if(timer) return;
timer = setTimeout(function () {
fn.apply(_this,args);
timer = null;
},delay);
};
}
// test
function testThrottle(e,content) {
console.log(e,content);
}
var testThrottleFn = throttle(testThrottle);
document.onmousemove = function(e) {
testThrottleFn(e,'throttle');
}
手写节流第二版
function throttle(fn,delay = 1000) {
let timer = null;
return(...args) => {
if(timer) return;
timer = setTimeout(() => {
fn(args);
timer = null;
},delay);
};
}
// test
function testThrottle(e,content) {
console.log(e,content);
}
var testThrottleFn = throttle(testThrottle);
document.onmousemove = function(e) {
testThrottleFn(e,'throttle');
}
同异比较
相同点:1.都使用setTimeout 2.目的都是降低回调的执行频率,节省资源
不同点: 1.防抖关注一定时间内连续触发的事件,只在最后一次执行;节流则侧重于一段时间内只执行一次
使用场景
防抖使用场景:连续的事件,只需触发一次回调的场景;
1.搜索框搜索输入,最后一次输入完成后,再次发送请求;
2.手机号,邮箱的输入验证;
3.窗口大小的调整,在调整完成后计算窗口大小,防止重复渲染;
节流使用场景:间隔一段时间执行一次回调的场景;
1.滚动加载,加载更多的操作;
2.表单的多次点击提交;
复习到的知识点:
1.setTimeout和clearTimeout的使用;
2.使用闭包获取变量,保证函数功能聚合;
3.apply的使用;
4.this指向问题;
5.箭头函数的使用;