🧑💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣
一、基本概念&简单实现
防抖【回城】
合并多次触发的事件为一次执行。常用于输入框的实时搜索。
1.应用
用于限制在短时间内频繁触发某个事件的执行次数。这种情况通常出现在用户在一个连续操作中多次触发同一事件,例如快速连续点击按钮、滚动页面时频繁触发事件等。
2.原理
防抖的基本思想是在事件触发后,设定一个等待时间(也称为延迟时间),只有在等待时间内没有再次触发该事件,才执行事件的回调函数。如果在等待时间内再次触发了该事件,就会重新计时等待时间。这样可以有效地阻止事件频繁触发,减少不必要的处理开销,提升前端性能和用户体验。
在实现防抖时,可以通过使用定时器(setTimeout)和清除定时器(clearTimeout)来实现。每次触发事件时,先清除之前设置的定时器,再设置一个新的定时器,从而达到防抖的效果。
3.手写
- 进入函数清除定时器
- debounce返回一个函数
- 清除函数后重置定时器
- 可以给函数传递参数
js
// 设置防抖
function debounce(callback, delay) {
let timer = null;
// debounce返回一个函数,进入先清空定时器,然后重置定时器
return function () {
// 清除定时器
clearTimeout(timer);
// 重置定时器
timer = setTimeout(() => {
callback(delay);
}, delay);
};
}
节流【技能释放】
限制函数的执行频率。在一段时间内只执行一次
1.应用
- 页面滚动事件:当用户滚动页面时,会频繁触发滚动事件。使用节流可以限制滚动事件的触发频率,避免过多的滚动事件处理,从而提高页面性能。
- 窗口大小调整事件:当用户调整浏览器窗口大小时,会频繁触发窗口大小调整事件。使用节流可以确保调整事件在一定时间间隔内只触发一次,避免过多的计算和布局更新。
- 输入框输入事件:当用户在输入框中输入内容时,每输入一个字符都会触发输入事件。使用节流可以控制输入事件的触发频率,减少输入事件的处理次数,特别适用于实时搜索功能。
- 鼠标移动事件:当鼠标在页面上移动时,会频繁触发鼠标移动事件。使用节流可以限制鼠标移动事件的触发次数,避免过多的计算和渲染。
- 按钮点击事件:当用户频繁点击按钮时,使用节流可以确保按钮点击事件在一定时间间隔内只触发一次,避免多次点击导致的重复操作。
- 自动完成(Autocomplete)功能:在输入框中实现自动完成功能时,用户每输入一个字符就会触发一次搜索请求。使用节流可以限制搜索请求的触发频率,避免频繁的搜索请求。
- 拖拽操作:当用户进行拖拽操作时,拖拽事件会持续触发。使用节流可以限制拖拽事件的触发频率,避免频繁的拖拽事件处理。
2.原理
不同于防抖,节流是确保在一定时间间隔内,事件最多触发一次,而不是在等待时间结束时执行一次。
节流函数的基本思想是,在事件触发后立即执行一次事件处理函数,并且在接下来的一段时间内阻止再次触发事件。只有在经过指定的时间间隔后,才允许再次触发事件,再次执行事件处理函数。
3.手写
- 先判断是否可以执行
- 阻止执行
- 执行函数
- 延迟时间设置为可执行
js
function throttle(callback) {
let canRun = true;
return function () {
// 已经有一个执行了,返回
if (!canRun) return;
// 阻止执行
canRun = false;
// 执行函数
callback("我节流了");
// 设置间隔时间
setTimeout(() => {
canRun = true;
}, 1000);
};
}
二、定时器实现我看烦了,其他方式呢?
节流
时间戳方式实现节流
原理: 通过记录上一次函数执行的时间戳,计算当前时间与上次执行时间的间隔是否大于设定的延迟时间,以决定是否执行函数。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Throttle with Timestamp</title>
</head>
<body>
<button id="throttle-btn">Click me</button>
<script>
function throttle(func, delay) {
let lastTime = 0; // 记录上次执行时间
return function(...args) {
const now = Date.now(); // 获取当前时间
if (now - lastTime >= delay) { // 判断时间间隔是否大于延迟时间
lastTime = now; // 更新上次执行时间
func.apply(this, args); // 执行函数
}
};
}
const handleClick = throttle(() => {
console.log('点击了Button');
}, 2000);
document.getElementById('throttle-btn').addEventListener('click', handleClick);
</script>
</body>
</html>
请求动画帧方式实现节流
原理: 利用 requestAnimationFrame
使得在浏览器每次重绘之前只执行一次函数,从而限制函数的执行频率,坏处是延迟时间无法自行控制,这里效果不是很好看出来,我们可以用时间戳简单模式下重绘的延时。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Throttle with requestAnimationFrame and setTimeout</title>
<style>
#throttle-area {
width: 100%;
height: 200px;
border: 1px solid black;
background-color: lightgray;
display: flex;
justify-content: center;
align-items: center;
font-size: 20px;
}
</style>
</head>
<body>
<div id="throttle-area">矩形中移动指针</div>
<script>
function throttle(func, delay) {
let ticking = false;
let lastExecution = 0;
return function (...args) {
const now = Date.now();
if (!ticking) {
requestAnimationFrame(() => {
if (now - lastExecution >= delay) { // 控制延迟时间
func.apply(this, args); // 执行函数
lastExecution = now; // 更新上次执行时间
}
ticking = false; // 重置标记
});
ticking = true; // 设置标记
}
};
}
const handleMouseMove = throttle((event) => {
const throttleArea = document.getElementById('throttle-area');
throttleArea.style.backgroundColor = `rgb(${event.clientX % 255}, ${event.clientY % 255}, 150)`;
throttleArea.textContent = `指针位置: (${event.clientX}, ${event.clientY})`;
console.log(`指针位置: (${event.clientX}, ${event.clientY})`);
}, 500); // 控制延迟时间为500毫秒
document.getElementById('throttle-area').addEventListener('mousemove', handleMouseMove);
</script>
</body>
</html>
防抖
立即执行一次+防抖
原理: 在函数首次调用时立即执行,用户停止输入后再次执行函数。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Debounce with Immediate Execution and Stop Typing</title>
</head>
<body>
<input type="text" id="debounce-input" placeholder="Type something...">
<script>
function debounce(func, delay) {
let timer; // 定时器变量
return function(...args) {
const context = this; // 保存函数执行上下文
clearTimeout(timer); // 清除定时器
const callNow = !timer; // 是否立即执行的标志
timer = setTimeout(function() { // 设置延迟定时器
timer = null; // 定时器置空
if (!callNow) { // 如果不是立即执行
func.apply(context, args); // 执行函数
}
}, delay);
if (callNow) { // 如果是立即执行
func.apply(context, args); // 执行函数
}
};
}
const handleInput = debounce((event) => { // 创建防抖函数
console.log('Input:', event.target.value); // 打印输入内容
}, 500); // 设置延迟时间为500毫秒
document.getElementById('debounce-input').addEventListener('input', handleInput); // 监听输入事件
</script>
</body>
</html>
用promise方式实现防抖
利用Promise来处理防抖逻辑,通过异步的方式实现函数的延迟执行。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Debounce with Promise</title>
</head>
<body>
<input type="text" id="debounce-input-promise" placeholder="Type something...">
<script>
function debounce(func, delay) {
let timer;
return function (...args) {
return new Promise((resolve) => {
if (timer) clearTimeout(timer); // 清除定时器
timer = setTimeout(() => {
resolve(func.apply(this, args)); // 延迟结束后执行函数
}, delay);
});
};
}
const handleInput = debounce((event) => {
console.log('Input:', event.target.value);
}, 1000);
document.getElementById('debounce-input-promise').addEventListener('input', handleInput);
</script>
</body>
</html>
用事件监听器实现防抖
原理: 利用事件监听器的绑定和解绑机制,避免频繁触发事件处理函数。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Debounce with Event Listener Unbinding</title>
</head>
<body>
<input type="text" id="debounce-event-input" placeholder="Type something...">
<script>
function debounceEvent(func, delay) {
let timer; // 定时器变量
return function(event) {
if (timer) { // 如果定时器存在,说明上次触发事件后还未执行函数
event.target.removeEventListener(event.type, arguments.callee); // 解绑事件
clearTimeout(timer); // 清除定时器
}
timer = setTimeout(() => { // 设置新的定时器
func.apply(this, arguments); // 执行函数
event.target.addEventListener(event.type, arguments.callee); // 重新绑定事件
timer = null; // 定时器置空
}, delay);
};
}
const handleInput = debounceEvent((event) => { // 创建防抖函数
console.log('Input:', event.target.value); // 打印输入内容
}, 1000); // 设置延迟时间为1000毫秒
document.getElementById('debounce-event-input').addEventListener('input', handleInput); // 监听输入事件
</script>
</body>
</html>
三、浏览器的事件循环
- 执行全局同步代码: 从全局作用域开始执行 JavaScript 代码,将同步任务依次压入执行栈中执行。
- 执行微任务: 在当前宏任务执行结束后,会检查微任务队列是否有任务,如果有则依次执行所有微任务,直到微任务队列为空。微任务的执行顺序是先进先出。
- 执行宏任务: 如果微任务队列为空,事件循环会从宏任务队列中取出一个任务,放入执行栈中执行。执行完当前宏任务后,会再次检查微任务队列,重复上述微任务执行的步骤。
- 渲染页面: 在执行完所有任务后,如果页面需要重绘或重排(比如有 DOM 变化或 CSS 样式变化),浏览器会进行页面渲染。
- 等待新任务: 一轮事件循环结束后,会等待新的任务进入任务队列,继续下一轮事件循环。
推荐阅读
开源电子书
工程化系列
本系列是一个从0到1的实现过程,如果您有耐心跟着实现,您可以实现一个完整的react18 + ts5 + webpack5 + 代码质量&代码风格检测&自动修复 + storybook8 + rollup + git action
实现的一个完整的组件库模板项目。如果您不打算自己配置,也可以直接clone组件库仓库切换到rollup_comp
分支即是完整的项目,当前实现已经足够个人使用,后续我们会新增webpack5优化、按需加载组件、实现一些常见的组件封装:包括但不限于拖拽排序、瀑布流、穿梭框、弹窗等
- 手把手带你搭建前端项目:react18、ts5、lint四剑客、webpack、storybook【保姆级教程一】
- 手把手带你搭建前端项目:react18、ts5、lint四剑客、webpack、storybook【保姆级教程二】
- 手把手带你搭建前端项目:react18、ts5、lint四剑客、webpack、storybook【保姆级教程三】
- 手把手带你搭建前端项目:react18、ts5、lint四剑客、webpack、storybook【保姆级教程四】
- 前端三大包管理器你知道多少?npm、yarn、pnpm
- 前端怎么可以不会GitHub Action一键部署?
面试手写系列
react实现原理系列
- 【react18原理探究实践】使用babel手搓探索下jsx的原理
- 【react18原理探究实践】上手调试源码探究jsx原理
- 【react18原理探究实践】图解react几个核心包之间的关联
- 【react18原理探究实践】react启动流程,其实就是创建三大全局对象
- 【react18原理探究实践】JS中的位运算&react中的lane模型
- 【react18原理探究实践】更新调度:如何统一更新
其他
🍋 写在最后
如果您看到这里了,并且觉得这篇文章对您有所帮助,希望您能够点赞👍和收藏⭐支持一下作者🙇🙇🙇,感谢🍺🍺!如果文中有任何不准确之处,也欢迎您指正,共同进步。感谢您的阅读,期待您的点赞👍和收藏⭐!
感兴趣的同学可以关注下我的公众号ObjectX前端实验室
🌟 少走弯路 | ObjectX前端实验室 🛠️「精选资源|实战经验|技术洞见」