
本文详解如何通过状态锁机制避免滚动事件中重复发起 ajax 请求,并提供可立即使用的修复代码及现代替代方案(intersection observer)。
本文详解如何通过状态锁机制避免滚动事件中重复发起 ajax 请求,并提供可立即使用的修复代码及现代替代方案(intersection observer)。
在实现“滚动到底部自动加载更多内容”功能时,一个常见问题是:$(window).scroll() 事件被高频触发(尤其在快速滚动或移动端),导致多个 AJAX 请求并发执行,不仅造成资源浪费、数据重复插入,还可能因后端响应顺序错乱引发 UI 错乱或逻辑错误。
根本原因在于:滚动监听本身不具备节流或防重入能力,每次满足条件就直接发起请求,而未校验前一次请求是否已完成。
✅ 正确做法:引入加载状态锁(Loading Flag)
最简洁有效的解决方案是使用一个布尔变量 loading 作为全局状态锁:
jQuery 1.12.4是jQuery 1.x系列的最后一个正式稳定版本,由jQuery团队于2016年发布。该版本主要面向需要兼容旧版浏览器环境的网站和Web应用,尤其适用于仍需支持Internet Explorer 6、Internet Explorer 7、Internet Explorer 8等老旧浏览器的项目。
- 请求发起前检查
loading === true,若为真则直接return,跳过本次执行; - 请求开始前设为
true,确保后续滚动事件被拦截; - 在
always回调中重置为false,无论成功或失败都释放锁,保证状态一致性。
var loading = false;
$(document).ready(function() {
$(window).scroll(function() {
// 防重入:若正在加载,直接退出
if (loading) return;
const lastID = $('.load-more').attr('lastID');
const $postList = $('#postList');
const windowHeight = $(window).height();
const scrollTop = $(window).scrollTop();
// 判断是否滚动到底部(兼容性写法)
if ($postList.height() <= scrollTop + windowHeight && lastID !== '0' && lastID !== undefined) {
loading = true; // 锁定加载状态
$.ajax({
type: 'POST',
url: 'getData.php',
data: { id: lastID }, // 推荐使用对象形式,更安全
beforeSend: function() {
$('.load-more').show();
},
success: function(html) {
$('.load-more').remove();
$postList.append(html);
},
error: function(xhr, status, error) {
console.warn('加载失败:', error);
// 可选:显示错误提示,或保留 load-more 元素供重试
},
always: function() {
loading = false; // 关键:务必在此处释放锁
}
});
}
});
});⚠️ 注意事项与优化建议
-
避免
attr('lastID')返回undefined:建议初始化时确保.load-more元素存在且含有效lastID属性,或增加空值判断(如!lastID || lastID == '0'); -
使用
data: { id: lastID }替代字符串拼接:防止 XSS 风险,jQuery 自动序列化并编码; -
考虑防抖(debounce)或节流(throttle):对极高频滚动场景(如桌面鼠标滚轮),可在
scroll外层加简单节流(如 Lodash 的_.throttle),但状态锁仍是必需基础; -
DOM 查询优化:将
$('#postList')、$(window)等高频访问对象缓存为变量(如示例中$postList),减少重复 DOM 查找开销; -
加载指示器健壮性:
beforeSend中显示.load-more,但若请求失败,应保留该元素并支持手动重试,提升用户体验。
? 更现代的替代方案:Intersection Observer API
jQuery 滚动监听本质是“被动轮询”,性能与精度有限。推荐渐进升级至原生 Intersection Observer API,它由浏览器原生优化,精准监听元素是否进入视口,无须绑定 scroll 事件,彻底规避高频触发问题:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !loading) {
loadMorePosts();
}
});
}, { threshold: 0.1 });
// 观察 .load-more 元素
observer.observe(document.querySelector('.load-more'));搭配相同的 loading 锁机制,即可实现高性能、低耦合的无限滚动。
总之,状态锁是解决滚动加载重复请求的最小可行方案;而面向未来,应优先采用 Intersection Observer 实现更优雅、更可持续的懒加载逻辑。

















