主页/jQuery教程/回调、延迟与异步/jQuery实战:构建基于Promise的图片懒加载组件

jQuery实战:构建基于Promise的图片懒加载组件

5,765字
24–37 分钟

概览

目录

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>

组件解析

上述示例的核心在于以下几点:

  1. 封装性与链式调用:插件通过 $.fn.lazyload 定义,返回 $.when.apply($, deferreds) 生成的 jQuery Promise对象,这允许外部使用 .done().fail() 等方法来监听所有图片加载的结果。
  2. 每个图片独立的Promise:在 $elements.each 循环中,为每张图片创建了一个 $.Deferred 对象,并将其存入 deferreds 数组。图片加载成功则调用 deferred.resolve(),失败则调用 deferred.reject()
  3. 聚合监控$.when.apply($, deferreds) 将所有独立的Promise合并成一个新的Promise。只有当数组中的所有Promise都被解决(resolved)时,新的Promise才会被解决;如果有任一Promise被拒绝(rejected),新的Promise立即被拒绝。这正是 jQuery Promise对象 在管理多个异步任务时的强大之处。
  4. 事件清理:通过 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所支持的最低浏览器版本,在这些环境中,该组件能够正常工作。

浏览器最低支持版本
Chrome30+
Edge12+
Firefox25+
Opera18+
Safari7+
Chrome Android30+
Firefox for Android25+
Opera Android18+
Safari on iOS7+
Samsung Internet4.0+
WebView Android4.4+
WebView on iOS7+

:上述版本基于jQuery 3.x系列的兼容性测试。对于jQuery 4.0.0的slim构建版本,由于移除了Deferred模块,本示例中的 $.Deferred$.when 方法将不可用,需改用完整构建版本或迁移至原生Promise。在更早的浏览器环境(如Internet Explorer 6-8)中,可以使用jQuery 1.x系列,但需要注意其Promise API的行为与后续版本存在差异。本示例展示了 jQuery Promise对象 在实际应用中的核心价值——通过统一的异步管理接口,简化复杂任务的协同与通知。