学习链接
一、函数节流(throttle)
函数节流:一个函数执行一次后,只有大于设定的执行周期后才会执行第二次。
有个需要频繁触发函数,出于优化性能角度,在规定时间内,只让函数触发的第一次生效,后面不生效。
版本一
function throttle(func, delay = 500) {
// 记录上一次函数触发的时间
let lastTime = 0; // 设为0 使得第一次可直接触发
return function(...args) {
// 记录当前函数触发的时间
let nowTime = Date.now();
if (nowTime - lastTime > delay) {
// 修正this指向问题
func.apply(this, args);
// 同步时间
lastTime = nowTime;
}
}
}
测试
html,body {
/* 使得滚动条出现 */
height: 500%;
}
function throttle(func, delay = 500) {
// 记录上一次函数触发的时间
let lastTime = 0; // 设为0 使得第一次可直接触发
return function(...args) {
// 记录当前函数触发的时间
let nowTime = Date.now();
if (nowTime - lastTime > delay) {
// 修正this指向问题
func.apply(this, args);
// 同步时间
lastTime = nowTime;
}
}
}
document.onscroll = throttle(function() {
console.log('scroll事件被触发了' + Date.now());
}, 1500);
版本二
// options.leading 来表示是否可以立即执行一次,
// opitons.trailing 表示结束调用的时候是否还要执行一次,默认都是 true。
// 不能同时将 leading 或 trailing 设置为 false 否则不会执行
function throttle(func, delay, options) {
let timer, context, args;
let lastTime = 0;
if (!options) options = {};
const later = function() {
lastTime = options.leading === false ? 0 : new Date().getTime();
timer = null;
func.apply(context, args);
};
const throttled = function(...args) {
const nowTime = new Date().getTime();
if (!lastTime && options.leading === false) { // 用===false 使得默认情况为true
lastTime = nowTime; // 开始时不调用
}
const remaining = delay - (nowTime - lastTime); // 本轮节流剩余时间
context = this; // later中使用
// <=0: 间隔已经超过节流时间 可执行
// >delay: 执行later后更新lastTime可能导致的情况 即重新启动计时执行第一次
if (remaining <= 0 || remaining > delay) {
if (timer) {
clearTimeout(timer); // 未结束调用 清空timer
timer = null;
}
lastTime = nowTime; // 更新上一次的时间
func.apply(context, args);
} else if (!timer && options.trailing !== false) { // 用!==false 使得默认情况为true
timer = setTimeout(later, remaining); // 结束时调用
}
};
return throttled;
}
document.onscroll = throttle(function() {
console.log('scroll事件被触发了' + Date.now());
}, 1500);
二、函数防抖(debounce)
防抖函数:一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效。
版本一
function debounce(func, delay = 300) {
// 存储计时器
let timer = null;
return function(...args) {
// 清除计时器
clearTimeout(timer);
// 重新计时
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
}
}
测试
<button id="btn">按钮</button>
<script type="text/javascript">
function debounce(func, delay = 300) {
// 存储计时器
let timer = null;
return function(...args) {
// 清除计时器
clearTimeout(timer);
// 重新计时
timer = setTimeout(() => {
console.log('this: ', this);
func.apply(this, args);
}, delay);
// 或者
// // 固定this指向
// const context = this;
// // 重新计时
// timer = setTimeout(function() {
// console.log('this: ', this);
// console.log('context: ', context);
// func.apply(context, args);
// }, delay);
}
}
document.getElementById('btn').onclick = debounce(function() {
console.log('点击事件被触发' + Date.now())
}, 1000)
</script>
版本二
immediate
是否可立即执行一次。
<button id="btn">按钮</button>
<button id="cancelBtn">取消</button>
<script type="text/javascript">
function debounce(func, wait = 500, immediate = false) {
let timeout = null, result = null;
const debounced = function (...args) {
// 固定this指向
let context = this;
// 清除计时器
clearTimeout(timeout);
if (immediate) { // 是否立即执行
// 如果已经执行过,不再执行
let callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
// 第一次立即执行
if(callNow) result = func.apply(context, args);
} else {
timeout = setTimeout(function(){
result = func.apply(context, args)
}, wait);
}
return result;
};
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
const setUseAction = debounce(function() {
console.log('点击事件被触发' + Date.now())
}, 1000, true);
// 使用防抖
document.getElementById('btn').onclick = setUseAction;
// 取消定时器
document.getElementById('cancelBtn').onclick = setUseAction.cancel;
</script>