Interview AiBoxInterview AiBox 实时 AI 助手,让你自信应答每一场面试
请手写一个代码实现
题型摘要
防抖和节流是前端性能优化的两种关键技术。防抖确保函数只在事件停止触发后执行一次,适用于搜索联想等场景;节流确保函数以固定频率执行,适用于滚动事件等场景。两者都通过控制函数执行频率来优化性能,但实现原理和应用场景有所不同。防抖使用延迟执行+重置计时,节流使用固定时间间隔执行。理解它们的原理和实现方式,能帮助我们在实际开发中做出正确的选择。
防抖与节流函数的手写实现
在前端开发中,防抖(Debounce)和节流(Throttle)是两种常用的性能优化技术,用于控制函数的执行频率,特别是在处理频繁触发的事件如resize、scroll、input等场景中。
防抖函数(Debounce)
概念与原理
防抖函数的核心思想是:在事件被触发后,等待一定时间间隔后再执行函数,如果在这个时间间隔内事件再次被触发,则重新计时。这样可以确保函数只在用户停止操作后执行一次。
代码实现
/**
* 防抖函数
* @param {Function} fn 需要防抖的函数
* @param {number} delay 延迟时间,单位毫秒
* @param {boolean} immediate 是否立即执行
* @return {Function} 防抖后的函数
*/
function debounce(fn, delay = 300, immediate = false) {
let timer = null;
let isInvoke = false;
function _debounce(...args) {
if (timer) clearTimeout(timer);
if (immediate && !isInvoke) {
fn.apply(this, args);
isInvoke = true;
} else {
timer = setTimeout(() => {
fn.apply(this, args);
isInvoke = false;
timer = null;
}, delay);
}
}
_debounce.cancel = function() {
if (timer) clearTimeout(timer);
timer = null;
isInvoke = false;
}
return _debounce;
}
应用场景
- 搜索框输入联想:用户在输入过程中不频繁发送请求,只在停止输入后发送一次请求
- 按钮点击事件:防止用户多次快速点击导致重复提交
- 窗口resize事件:窗口调整结束后再执行相关操作
节流函数(Throttle)
概念与原理
节流函数的核心思想是:在一定时间间隔内只执行一次函数,无论这个时间间隔内事件触发了多少次。这样可以确保函数以固定的频率执行。
代码实现
/**
* 节流函数
* @param {Function} fn 需要节流的函数
* @param {number} interval 时间间隔,单位毫秒
* @param {Object} options 配置项
* @param {boolean} options.leading 是否在开始时立即执行
* @param {boolean} options.trailing 是否在结束时执行最后一次
* @return {Function} 节流后的函数
*/
function throttle(fn, interval = 300, options = { leading: true, trailing: true }) {
let timer = null;
let lastTime = 0;
const { leading, trailing } = options;
function _throttle(...args) {
const nowTime = Date.now();
// 首次执行或不立即执行的情况
if (!lastTime && !leading) lastTime = nowTime;
const remainTime = interval - (nowTime - lastTime);
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
fn.apply(this, args);
lastTime = nowTime;
return;
}
if (trailing && !timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
lastTime = leading ? Date.now() : 0;
}, remainTime);
}
}
_throttle.cancel = function() {
if (timer) clearTimeout(timer);
timer = null;
lastTime = 0;
}
return _throttle;
}
应用场景
- 页面滚动事件:控制滚动事件的触发频率,提高性能
- 鼠标移动事件:如拖拽、跟随效果等
- 播放进度条:定期更新播放进度
- 高频点击事件:限制点击频率
防抖与节流的对比
| 特性 | 防抖(Debounce) | 节流(Throttle) |
|---|---|---|
| 执行时机 | 事件停止触发后一段时间 | 固定时间间隔内执行一次 |
| 执行次数 | 可能只执行一次 | 至少执行一次,可能多次 |
| 适用场景 | 只关心最终结果 | 关心过程和结果 |
| 实现原理 | 延迟执行+重置计时 | 固定时间间隔执行 |
使用示例
// 防抖示例:搜索框输入联想
const searchInput = document.getElementById('search');
const searchDebounce = debounce(function(e) {
console.log('发送搜索请求:', e.target.value);
// 实际项目中这里可能是API请求
}, 500);
searchInput.addEventListener('input', searchDebounce);
// 节流示例:页面滚动事件
const scrollThrottle = throttle(function() {
console.log('页面滚动位置:', window.scrollY);
// 实际项目中这里可能是加载更多内容或更新UI
}, 200);
window.addEventListener('scroll', scrollThrottle);
防抖与节流的可视化理解
实现细节解析
防抖函数实现要点
- 使用闭包保存计时器:确保每次调用的是同一个计时器
- 清除之前的计时器:每次触发事件时清除之前的计时器,重新计时
- 立即执行选项:通过
immediate参数控制是否在第一次触发时立即执行 - 取消功能:提供
cancel方法可以取消待执行的防抖函数
节流函数实现要点
- 时间戳判断:通过记录上一次执行的时间戳,判断是否达到执行间隔
- 计时器补充:对于最后一次触发,使用计时器确保执行
- 首尾执行控制:通过
leading和trailing参数控制是否在开始和结束时执行 - 取消功能:提供
cancel方法可以取消待执行的节流函数
进阶实现:结合requestAnimationFrame
对于动画相关的场景,可以使用requestAnimationFrame实现更高效的节流函数:
function rafThrottle(fn) {
let locked = false;
return function(...args) {
if (locked) return;
locked = true;
requestAnimationFrame(() => {
fn.apply(this, args);
locked = false;
});
};
}
// 使用示例
const moveThrottle = rafThrottle(function(e) {
console.log('鼠标移动:', e.clientX, e.clientY);
});
document.addEventListener('mousemove', moveThrottle);
总结
防抖和节流是前端性能优化的重要手段,它们通过控制函数执行频率来提高应用性能。防抖适合"只关心结果"的场景,如搜索联想;节流适合"关心过程"的场景,如滚动加载。理解它们的原理和实现方式,能够帮助我们在实际开发中做出正确的选择。
参考资料
思维导图
Interview AiBoxInterview AiBox — 面试搭档
不只是准备,更是实时陪练
Interview AiBox 在面试过程中提供实时屏幕提示、AI 模拟面试和智能复盘,让你每一次回答都更有信心。
AI 助读
一键发送到常用 AI
防抖和节流是前端性能优化的两种关键技术。防抖确保函数只在事件停止触发后执行一次,适用于搜索联想等场景;节流确保函数以固定频率执行,适用于滚动事件等场景。两者都通过控制函数执行频率来优化性能,但实现原理和应用场景有所不同。防抖使用延迟执行+重置计时,节流使用固定时间间隔执行。理解它们的原理和实现方式,能帮助我们在实际开发中做出正确的选择。
智能总结
深度解读
考点定位
思路启发
相关题目
请详细解释JavaScript中var、let和const关键字之间的区别
JavaScript中var、let和const的主要区别在于:1)作用域不同(var是函数作用域,let和const是块级作用域);2)变量提升行为不同(var存在变量提升,let和const存在暂时性死区);3)重复声明规则不同(var允许,let和const不允许);4)初始化要求不同(const必须初始化,var和let可选);5)重新赋值规则不同(const基本类型不可重新赋值);6)全局对象属性不同(var会成为全局对象属性,let和const不会)。现代JavaScript开发推荐优先使用const,需要重新赋值时使用let,避免使用var。
如何优化防抖函数,避免重复创建定时器?
防抖函数优化主要解决重复创建定时器导致的内存开销问题。优化方案包括:1)定时器复用优化,避免每次调用都创建新定时器;2)添加取消机制,防止内存泄漏;3)立即执行选项,提高灵活性;4)记忆返回值优化,缓存执行结果;5)使用类实现,提供完整API和更好的内存管理。最佳实践是根据场景复杂度选择合适方案,几乎所有场景都应提供取消方法,并考虑是否需要立即执行和返回值处理。
请解释JavaScript中的模块化概念,以及CommonJS、AMD、ES模块等模块化方案的异同。
JavaScript模块化是将代码分解为独立、可重用单元的技术,解决命名冲突、依赖管理和代码组织问题。主要模块化方案包括: 1. **CommonJS**:Node.js采用的同步模块系统,使用require和module.exports,适合服务端环境,但浏览器不友好。 2. **AMD**:异步模块定义,专为浏览器设计,使用define和require回调,避免阻塞,但语法复杂。 3. **ES模块**:ECMAScript官方标准,使用import/export语法,支持静态分析和实时绑定,同时适用于浏览器和服务端,是未来发展方向。 三者核心区别在于加载机制(同步/异步)、语法设计、值处理方式(拷贝/引用)和适用环境。ES模块凭借官方标准地位和现代化特性正成为主流选择。
判断JavaScript数据类型的方法有哪些?
JavaScript中判断数据类型的方法主要有:1) `typeof`:简单直接,适合基本类型,但null返回"object",引用类型都返回"object";2) `instanceof`:适合判断对象类型,但不能用于基本类型,跨窗口可能有问题;3) `Object.prototype.toString.call()`:最准确可靠的方法,能判断所有类型;4) `constructor`属性:能区分大多数类型,但null/undefined会报错,可被修改;5) `Array.isArray()`:专门用于判断数组;6) 自定义类型判断函数:基于上述方法封装更通用的判断函数;7) 鸭子类型:关注对象行为而非类型,更灵活但不严格。实际应用中,基本类型用`typeof`,数组用`Array.isArray()`,精确判断用`Object.prototype.toString.call()`,通用场景可自定义函数。
请解释JavaScript中的宏任务和微任务概念?
JavaScript中的宏任务和微任务是事件循环机制的核心概念。宏任务包括整体脚本、setTimeout、setInterval、I/O操作等,在事件循环中按顺序执行。微任务包括Promise.then、async/await、MutationObserver等,优先级高于宏任务,会在当前宏任务执行后立即执行。执行顺序为:同步代码 → 微任务 → 宏任务,微任务队列清空后才会执行下一个宏任务。理解这一机制对于编写高效的异步代码至关重要,可用于优化代码执行顺序、避免阻塞UI渲染、确保DOM更新完成后再执行操作等场景。