概览
jQuery图片懒加载是一种优化页面性能的常用技术,其核心原理是仅当图片即将进入可视区域时才加载真实资源,从而减少初始请求数、节省流量。本实战项目将综合运用 jQuery Promise对象 与延迟对象(Deferred)体系,构建一个具备异步流程控制能力的懒加载组件。通过将图片加载过程转化为Promise,可以优雅地管理多个图片的加载状态,并在所有图片完成加载后执行后续逻辑(如隐藏加载提示、触发页面重绘等)。这不仅是一次技术实践,更是对前序章节中异步编程理论的巩固与深化。
组件设计思路
一个健壮的懒加载组件需要满足以下功能点:检测图片是否进入可视区域;动态替换 data-src 属性中的真实图片地址;处理图片加载成功与失败的状态;提供加载完成后的全局通知。
利用 jQuery Promise对象 的优势在于,可以将每个图片的加载操作封装为一个独立的Promise,进而通过 $.when() 方法监控所有图片的加载进度。当所有图片成功加载或某个图片失败时,组件能统一响应。同时,这种设计也使得组件易于扩展,例如可以轻松添加加载超时、重试机制等高级功能。
构建懒加载组件
以下逐步实现一个基于Promise的图片懒加载组件。该组件将监听页面滚动,对符合条件的图片执行加载,并为每次加载返回Promise。
核心逻辑实现
首先,需要定义组件的默认配置,并利用 $.fn 将其扩展为jQuery实例方法,以便于链式调用和重用。
语法概览
$.fn.lazyload = function(options) {
var defaults = { /* 默认参数 */ };
var settings = $.extend({}, defaults, options);
// 返回 Promise 对象,用于外部监控
return this.each(function() {
// 为每个元素创建并管理 Promise
}).promise(); // 返回集合的 Promise,表示所有元素处理完成
};参数说明
options:用户传入的配置对象,可覆盖默认参数。defaults:组件的默认设置,如加载偏移量、加载完成后的回调等。
示例:完整的图片懒加载组件
以下示例创建了一个完整的HTML页面,包含一个拥有大量图片的列表,并通过自定义的 lazyload 插件实现懒加载。插件内部为每张图片创建了Deferred对象,并使用 $.when 监控所有图片的加载情况。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>jQuery 实战:基于Promise的图片懒加载</title>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
h1 { text-align: center; }
.image-container {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
.image-item {
width: 200px;
height: 200px;
margin: 15px;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
border: 1px solid #ddd;
border-radius: 4px;
}
.image-item img {
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0;
transition: opacity 0.3s;
}
.image-item img.loaded {
opacity: 1;
}
.image-item .placeholder {
color: #999;
font-size: 14px;
}
#loading-status {
position: fixed;
top: 10px;
right: 10px;
padding: 8px 12px;
background: #333;
color: white;
border-radius: 4px;
display: none;
}
</style>
</head>
<body>
<h1>基于Promise的图片懒加载示例</h1>
<div id="loading-status">正在加载图片...</div>
<div class="image-container" id="lazy-container">
<!-- 以下图片的真实地址存放在 data-src 属性中 -->
</div>
<script>
(function($) {
// 定义懒加载插件
$.fn.lazyload = function(options) {
var defaults = {
threshold: 100, // 预加载偏移量,距离可视区域多少像素时开始加载
container: $(window), // 滚动的容器,默认为窗口
loadClass: 'loaded' // 图片加载完成后添加的类名
};
var settings = $.extend({}, defaults, options);
var $elements = this; // 当前 jQuery 集合(图片元素)
var deferreds = []; // 用于存储每个图片加载的 Deferred 对象
// 检查元素是否在可视区域内
function isElementInViewport($el) {
var elementTop = $el.offset().top;
var elementBottom = elementTop + $el.outerHeight();
var viewportTop = $(window).scrollTop();
var viewportBottom = viewportTop + $(window).height();
return elementBottom >= viewportTop - settings.threshold && elementTop <= viewportBottom + settings.threshold;
}
// 处理单个图片的加载
function loadImage($img, deferred) {
var src = $img.data('src');
if (!src) {
deferred.reject('图片未提供 data-src 属性');
return;
}
// 创建临时 Image 对象用于预加载
var tempImage = new Image();
tempImage.onload = function() {
$img.attr('src', src).addClass(settings.loadClass);
deferred.resolve();
};
tempImage.onerror = function() {
$img.after('<span class="error">加载失败</span>');
deferred.reject('图片加载失败:' + src);
};
tempImage.src = src;
}
// 遍历每个图片,为它们创建 Deferred
$elements.each(function() {
var $img = $(this);
var deferred = $.Deferred();
deferreds.push(deferred);
// 如果图片已经在可视区域内,立即加载
if (isElementInViewport($img)) {
loadImage($img, deferred);
} else {
// 否则,绑定滚动事件检查
settings.container.on('scroll.lazyload', function() {
if (isElementInViewport($img)) {
settings.container.off('scroll.lazyload', loadHandler); // 解绑当前图片的监听
loadImage($img, deferred);
}
});
}
});
// 返回一个 Promise,当所有图片的 Deferred 都解决时,它才解决
return $.when.apply($, deferreds).always(function() {
settings.container.off('scroll.lazyload'); // 清理所有滚动事件
});
};
})(jQuery);
// 页面加载后生成图片列表
$(document).ready(function() {
var $container = $('#lazy-container');
// 生成 20 张测试图片,使用占位图片服务
for (var i = 1; i <= 20; i++) {
var imageUrl = 'https://via.placeholder.com/200x150?text=Image+' + i;
var $item = $('<div class="image-item"></div>');
var $img = $('<img alt="Lazy image ' + i + '" class="lazy-image">');
// 将真实地址放在 data-src 中,src 留空或放一个占位图
$img.attr('data-src', imageUrl);
$item.append($img);
$container.append($item);
}
// 调用懒加载插件,并获取返回的 Promise
var loadPromise = $('.lazy-image').lazyload({
threshold: 150,
loadClass: 'loaded'
});
// 显示加载状态
$('#loading-status').fadeIn();
// 使用 Promise 的 done 方法来处理所有图片加载完成后的逻辑
loadPromise.done(function() {
$('#loading-status').text('所有图片加载完成!').delay(2000).fadeOut();
}).fail(function() {
$('#loading-status').text('部分图片加载失败!').css('background', '#c00');
});
});
</script>
</body>
</html>组件解析
上述示例的核心在于以下几点:
- 封装性与链式调用:插件通过
$.fn.lazyload定义,返回$.when.apply($, deferreds)生成的 jQuery Promise对象,这允许外部使用.done()、.fail()等方法来监听所有图片加载的结果。 - 每个图片独立的Promise:在
$elements.each循环中,为每张图片创建了一个$.Deferred对象,并将其存入deferreds数组。图片加载成功则调用deferred.resolve(),失败则调用deferred.reject()。 - 聚合监控:
$.when.apply($, deferreds)将所有独立的Promise合并成一个新的Promise。只有当数组中的所有Promise都被解决(resolved)时,新的Promise才会被解决;如果有任一Promise被拒绝(rejected),新的Promise立即被拒绝。这正是 jQuery Promise对象 在管理多个异步任务时的强大之处。 - 事件清理:通过
always方法确保无论成功或失败,都会移除滚动事件监听,避免内存泄漏。
版本变更记录
下表梳理了jQuery版本迭代中与Promise和异步操作相关的重要变更,这些直接影响上述懒加载组件的设计与兼容性。
| 版本 | 变更内容与影响 |
|---|---|
| 1.5 | 引入 Deferred 对象和 Promise 概念。$.ajax() 开始返回 jQuery Promise对象,为异步流程控制奠定基础。 |
| 1.6 | 增加了 .promise() 方法,允许为集合上的动画队列创建Promise。 |
| 1.8 | .deferred.pipe() 方法被废弃,推荐使用 .then() 进行Promise的链式过滤和链接,使Promise行为更贴近标准。 |
| 3.0 | 更新了Promise的行为以更好地兼容Promises/A+规范。例如,由 .then() 创建的新Promise的异常处理方式更加符合标准,这提升了代码的可预测性。 |
| 4.0 | 重大变更:在“slim”构建版本中移除了Deferred和Callbacks模块。这意味着如果使用slim版本,$.Deferred、$.when 以及基于它们的懒加载组件将无法工作。官方建议在大多数场景下使用原生Promise替代。对于需要支持IE11或依赖特定Deferred功能的项目,应使用完整构建版本。 |
浏览器兼容状态
本实战项目构建的懒加载组件依赖于jQuery的Deferred/Promise体系及DOM操作方法,其兼容性与jQuery核心库保持一致。下表列出了jQuery所支持的最低浏览器版本,在这些环境中,该组件能够正常工作。
| 浏览器 | 最低支持版本 |
|---|---|
| Chrome | 30+ |
| Edge | 12+ |
| Firefox | 25+ |
| Opera | 18+ |
| Safari | 7+ |
| Chrome Android | 30+ |
| Firefox for Android | 25+ |
| Opera Android | 18+ |
| Safari on iOS | 7+ |
| Samsung Internet | 4.0+ |
| WebView Android | 4.4+ |
| WebView on iOS | 7+ |
注:上述版本基于jQuery 3.x系列的兼容性测试。对于jQuery 4.0.0的slim构建版本,由于移除了Deferred模块,本示例中的
$.Deferred和$.when方法将不可用,需改用完整构建版本或迁移至原生Promise。在更早的浏览器环境(如Internet Explorer 6-8)中,可以使用jQuery 1.x系列,但需要注意其Promise API的行为与后续版本存在差异。本示例展示了 jQuery Promise对象 在实际应用中的核心价值——通过统一的异步管理接口,简化复杂任务的协同与通知。
