jQuery性能优化

jQuery性能优化是提升Web应用响应速度和用户体验的关键。通过合理的优化技巧,可以显著减少页面加载时间、提高交互流畅度。

1. 选择器优化

选择器是jQuery中最常用的功能之一,不恰当的使用会严重影响性能。

性能测试:选择器对比

点击按钮测试不同选择器的性能...

DOM结构示例:
<div id="container">
  <ul class="product-list">
    <li class="product-item">产品1</li>
    <li class="product-item">产品2</li>
    <!-- 更多li... -->
  </ul>
  <div class="sidebar">
    <p class="highlight">内容</p>
  </div>
</div>

1.1 ID选择器优先

ID选择器是最高效的,因为浏览器有原生方法getElementById()


// ❌ 低效 - 使用类选择器查找ID元素
$(".myId"); // 浏览器会遍历所有元素查找类名

// ✅ 高效 - 直接使用ID选择器
$("#myId"); // 浏览器直接使用getElementById()

// ❌ 低效 - 不必要的上下文
$("#container .myClass"); // 如果.container就是#container,这是多余的

// ✅ 高效 - 直接从ID开始
$("#myId").find(".myClass");
// 或者
$(".myClass", $("#myId")); // 指定上下文
                            

1.2 避免通用选择器过度使用

通用选择器*会匹配所有元素,应尽量避免。


// ❌ 低效 - 通用选择器开头
$("* .myClass"); // 先匹配所有元素,再找子元素中的.myClass

// ❌ 低效 - 在复杂选择器中用通用选择器
$("#container * .myClass");

// ✅ 高效 - 直接指定父元素
$("#container .myClass");

// ❌ 低效 - 不必要的通用选择器
$("div#container *"); // #container已经是div,*是多余的

// ✅ 高效
$("#container").children();
// 或者更具体
$("#container > *");
                            

1.3 右向左 vs 左向右

jQuery选择器是从右向左解析的,最右边的选择器是关键。


// ❌ 低效 - 右边的选择器太宽泛
$("#container div .myClass");
// 解析过程:1. 找到所有.myClass 2. 检查父元素是否有div 3. 检查是否在#container内

// ✅ 高效 - 右边的选择器更具体
$("#container .myClass");
// 或者
$(".myClass", "#container");

// 性能对比测试
console.time('低效选择器');
$("#container div .myClass").length;
console.timeEnd('低效选择器'); // 可能较慢

console.time('高效选择器');
$("#container").find(".myClass").length;
console.timeEnd('高效选择器'); // 更快
                            

1.4 缓存选择器结果

重复使用选择器时应缓存结果,避免重复查询DOM。


// ❌ 低效 - 重复查询DOM
for(let i = 0; i < 100; i++) {
    $(".myClass").css("color", "red"); // 每次循环都查询DOM
}

// ✅ 高效 - 缓存选择器结果
const $myClass = $(".myClass"); // 只查询一次
for(let i = 0; i < 100; i++) {
    $myClass.css("color", "red");
}

// ✅ 更高效 - 在循环外部操作
$(".myClass").css("color", "red"); // 一次性操作

// 复杂场景中的缓存
function updateUI() {
    // 缓存常用的选择器
    const $header = $(".header");
    const $content = $(".content");
    const $footer = $(".footer");

    // 多次使用缓存的对象
    $header.addClass("active");
    $content.height($(window).height() - $header.height() - $footer.height());
    $footer.show();
}

// 页面级缓存
const App = {
    elements: {
        $header: null,
        $content: null,
        $footer: null
    },

    init: function() {
        // 初始化时缓存所有常用元素
        this.elements.$header = $(".header");
        this.elements.$content = $(".content");
        this.elements.$footer = $(".footer");

        // 后续直接使用缓存
        this.updateLayout();
    },

    updateLayout: function() {
        const h = this.elements.$header.height();
        const f = this.elements.$footer.height();
        this.elements.$content.height($(window).height() - h - f);
    }
};
                            

1.5 使用原生方法

在某些情况下,原生DOM方法比jQuery更快。


// 性能对比
const $element = $("#myElement");

// jQuery方法
console.time('jQuery text');
const text1 = $element.text();
console.timeEnd('jQuery text');

// 原生方法
console.time('原生 text');
const text2 = $element[0].textContent;
console.timeEnd('原生 text');

// 选择器性能对比
console.time('jQuery选择器');
$("#myId .myClass");
console.timeEnd('jQuery选择器');

console.time('原生选择器');
document.querySelectorAll("#myId .myClass");
console.timeEnd('原生选择器');

// 实际应用:混合使用
function getElementText(id) {
    // 对于ID选择器,原生方法更快
    const element = document.getElementById(id);
    if(element) {
        return element.textContent;
    }
    return "";
}

// 批量操作时使用原生方法
const elements = document.querySelectorAll(".myClass");
for(let i = 0; i < elements.length; i++) {
    // 直接操作DOM元素,避免jQuery开销
    elements[i].style.color = "red";
}

// 需要jQuery功能时再转换
$(elements).addClass("processed");
                            
选择器性能规则:
  1. 优先使用ID选择器:$("#id")
  2. 避免从通用选择器开始:$("* .class")
  3. 缓存重复使用的选择器结果
  4. 尽量指定上下文范围
  5. 在循环中避免重复查询DOM

2. DOM操作优化

DOM操作是Web性能的主要瓶颈之一,优化DOM操作可以显著提升性能。

DOM操作性能测试:
性能对比结果:
DocumentFragment 直接操作DOM

2.1 批量操作DOM

减少DOM操作次数,尽量批量处理。


// ❌ 低效 - 多次操作DOM
for(let i = 0; i < 100; i++) {
    $("#container").append(`
Item ${i}
`); // 每次append都触发重排 } // ✅ 高效 - 批量操作 let html = ""; for(let i = 0; i < 100; i++) { html += `
Item ${i}
`; } $("#container").append(html); // 一次操作 // ✅ 更高效 - 使用DocumentFragment const fragment = document.createDocumentFragment(); for(let i = 0; i < 100; i++) { const div = document.createElement("div"); div.textContent = `Item ${i}`; fragment.appendChild(div); } $("#container")[0].appendChild(fragment); // 复杂元素的批量创建 function createUserList(users) { const $list = $("
    ").addClass("user-list"); // 创建文档片段 const fragment = document.createDocumentFragment(); users.forEach(user => { const li = document.createElement("li"); li.className = "user-item"; li.innerHTML = `

    ${user.name}

    ${user.email}

    ${user.role} `; fragment.appendChild(li); }); $list[0].appendChild(fragment); return $list; }

2.2 离线操作DOM

在操作完成前将元素从DOM中移除,操作完成后再添加回去。


// 批量修改元素样式
function updateElementsStyle($elements) {
    // 1. 将元素从DOM中移除(离线操作)
    const elementsArray = $elements.toArray();
    const parent = $elements[0].parentNode;

    // 2. 离线操作
    elementsArray.forEach(element => {
        // 这里操作不会触发重排
        element.style.color = "red";
        element.style.backgroundColor = "yellow";
        element.className = "updated";
    });

    // 3. 一次性添加回DOM(只触发一次重排)
    elementsArray.forEach(element => {
        parent.appendChild(element);
    });
}

// 更优雅的方式 - 使用detach()
function optimizeUpdate($elements) {
    // 从DOM中分离元素(保留数据和事件)
    const $detached = $elements.detach();

    // 离线操作
    $detached.css({
        color: "red",
        backgroundColor: "yellow"
    }).addClass("updated");

    // 重新插入
    $("#container").append($detached);
}

// 隐藏元素进行操作
function updateHidden($element) {
    // 隐藏元素
    $element.hide();

    // 进行操作(不会触发重绘)
    $element.css({
        width: "100%",
        height: "200px",
        padding: "20px"
    }).addClass("processed");

    // 显示元素(只触发一次重绘)
    $element.show();
}
                            

2.3 减少重排和重绘

重排(reflow)和重绘(repaint)是性能杀手,应尽量减少。


// ❌ 低效 - 多次触发重排
$("#element")
    .css("width", "100px")   // 触发重排
    .css("height", "200px")  // 触发重排
    .css("padding", "10px"); // 触发重排

// ✅ 高效 - 一次设置所有样式
$("#element").css({
    width: "100px",
    height: "200px",
    padding: "10px"
}); // 只触发一次重排

// ❌ 低效 - 读写分离(强制同步重排)
const width = $("#element").width(); // 读 - 触发重排以获取准确值
$("#element").css("width", width + 100 + "px"); // 写 - 再次触发重排

// ✅ 高效 - 批量读写
// 方法1:使用requestAnimationFrame
function updateElement() {
    requestAnimationFrame(() => {
        const $element = $("#element");
        // 读取
        const width = $element.width();
        const height = $element.height();

        // 批量写入
        $element.css({
            width: width + 100 + "px",
            height: height + 50 + "px"
        });
    });
}

// 方法2:使用cssText(原生方法)
const element = $("#element")[0];
element.style.cssText += ";width:200px;height:100px;padding:10px;";

// 方法3:添加class(最推荐)
$("#element").addClass("new-style");

// CSS
.new-style {
    width: 200px;
    height: 100px;
    padding: 10px;
    /* 所有样式一次性应用 */
}
                            

2.4 优化元素遍历


// ❌ 低效 - 在循环中查询DOM
$(".item").each(function() {
    const $children = $(this).find(".child"); // 每次循环都查询
    // ...
});

// ✅ 高效 - 预查询
const $children = $(".item .child"); // 一次查询
$(".item").each(function(index) {
    const $child = $children.eq(index);
    // ...
});

// ✅ 更高效 - 使用原生循环
const items = document.querySelectorAll(".item");
const children = document.querySelectorAll(".item .child");

for(let i = 0; i < items.length; i++) {
    const child = children[i];
    // 直接操作DOM元素
    child.style.color = "red";
}

// 使用while循环(最快)
let i = items.length;
while(i--) {
    const item = items[i];
    // 倒序操作
    item.classList.add("processed");
}

// 链式操作优化
$(".item")
    .addClass("highlight")  // 一次操作
    .css("color", "red")    // 一次操作
    .fadeIn(300);           // 一次操作

// 避免在链式调用中重复查找
$(".item")
    .filter(":visible")     // 过滤
    .addClass("active")     // 添加类
    .end()                  // 回到原始集合
    .hide();                // 隐藏所有
                            

3. 事件处理优化

事件处理不当会导致内存泄漏和性能问题。

事件委托演示:
  • 项目1
  • 项目2
点击列表项或删除按钮,查看事件委托效果
事件处理对比:
传统绑定:
  • 每个元素都绑定事件
  • 动态添加元素无效
  • 内存占用高
事件委托:
  • 一个事件监听器
  • 支持动态元素
  • 内存占用低
内存占用: 20%

3.1 使用事件委托


// ❌ 低效 - 为每个元素绑定事件
$(".delete-btn").click(function() {
    $(this).closest(".item").remove();
});

// ✅ 高效 - 事件委托
$("#container").on("click", ".delete-btn", function() {
    $(this).closest(".item").remove();
});

// 动态添加的元素也能响应事件
function addNewItem(text) {
    $("#container").append(`
        
${text}
`); // 无需重新绑定事件,因为使用了事件委托 } // 多个事件类型 $("#container").on("click mouseenter mouseleave", ".item", function(event) { switch(event.type) { case "click": $(this).toggleClass("active"); break; case "mouseenter": $(this).addClass("hover"); break; case "mouseleave": $(this).removeClass("hover"); break; } }); // 性能对比:1000个元素 console.time('传统绑定'); for(let i = 0; i < 1000; i++) { $(`#item-${i}`).click(handler); } console.timeEnd('传统绑定'); console.time('事件委托'); $("#container").on("click", ".item", handler); console.timeEnd('事件委托');

3.2 节流和防抖


// 防抖函数:连续触发时只执行最后一次
function debounce(func, wait) {
    let timeout;
    return function() {
        const context = this;
        const args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            func.apply(context, args);
        }, wait);
    };
}

// 节流函数:一定时间内只执行一次
function throttle(func, limit) {
    let inThrottle;
    return function() {
        const context = this;
        const args = arguments;
        if(!inThrottle) {
            func.apply(context, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

// 应用示例
// resize事件 - 使用防抖
$(window).on("resize", debounce(function() {
    console.log("窗口大小改变", $(window).width());
    updateLayout();
}, 250));

// scroll事件 - 使用节流
$(window).on("scroll", throttle(function() {
    const scrollTop = $(window).scrollTop();
    updateNavigation(scrollTop);
}, 100));

// 输入框搜索 - 使用防抖
$("#search-input").on("input", debounce(function() {
    const query = $(this).val();
    search(query);
}, 300));

// 按钮点击 - 防止重复提交(节流)
$("#submit-btn").on("click", throttle(function() {
    submitForm();
}, 2000)); // 2秒内只允许提交一次

// jQuery插件版节流/防抖
$.extend({
    debounce: function(func, wait, immediate) {
        let timeout;
        return function() {
            const context = this;
            const args = arguments;
            const later = function() {
                timeout = null;
                if(!immediate) func.apply(context, args);
            };
            const callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if(callNow) func.apply(context, args);
        };
    },

    throttle: function(func, limit) {
        let lastFunc;
        let lastRan;
        return function() {
            const context = this;
            const args = arguments;
            if(!lastRan) {
                func.apply(context, args);
                lastRan = Date.now();
            } else {
                clearTimeout(lastFunc);
                lastFunc = setTimeout(function() {
                    if((Date.now() - lastRan) >= limit) {
                        func.apply(context, args);
                        lastRan = Date.now();
                    }
                }, limit - (Date.now() - lastRan));
            }
        };
    }
});

// 使用jQuery扩展
$("#element").on("scroll", $.throttle(scrollHandler, 100));
                            

3.3 事件命名空间


// 使用命名空间管理事件
// 绑定事件
$("#element").on("click.myNamespace", function() {
    console.log("点击事件 - myNamespace");
});

$("#element").on("click.otherNamespace", function() {
    console.log("点击事件 - otherNamespace");
});

// 移除特定命名空间的事件
$("#element").off("click.myNamespace"); // 只移除myNamespace的点击事件

// 移除所有命名空间的事件
$("#element").off("click"); // 移除所有点击事件

// 插件开发中的事件命名空间
(function($) {
    $.fn.myPlugin = function() {
        return this.each(function() {
            const $this = $(this);

            // 使用插件名作为命名空间
            $this.on("click.myPlugin", function() {
                // 处理点击
            });

            $this.on("mouseenter.myPlugin", function() {
                // 处理鼠标进入
            });
        });
    };

    // 销毁方法
    $.fn.myPlugin.destroy = function(element) {
        $(element).off(".myPlugin"); // 移除插件所有事件
    };
})(jQuery);

// 一次性事件
$("#element").one("click", function() {
    console.log("只执行一次");
});

// 带命名空间的一次性事件
$("#element").one("click.myPlugin", function() {
    console.log("插件相关的一次性事件");
});

// 事件委托 + 命名空间
$("#container").on("click.myApp", ".item", function() {
    console.log("委托事件");
});

// 性能优化:移除不需要的事件
function cleanupEvents() {
    // 移除所有.myApp命名空间的事件
    $(document).off(".myApp");

    // 移除特定元素的所有事件
    $("#temp-element").off();

    // 移除特定类型和命名空间的事件
    $(".dynamic-elements").off("click.handler");
}
                            

4. 动画与特效优化

动画和特效处理不当会导致页面卡顿,影响用户体验。

动画性能测试:
CSS3
jQuery
CSS3 FPS: 60
jQuery FPS: 60
性能监控:
动画类型 FPS CPU占用 流畅度
CSS3动画 60 优秀
jQuery动画 60 良好
CSS3动画由GPU加速,性能更好。对于复杂动画,优先使用CSS3。

4.1 优先使用CSS3动画


/* CSS3动画 - GPU加速 */
.animate-css3 {
    transition: all 0.3s ease;
    transform: translateZ(0); /* 触发GPU加速 */
}

.animate-css3:hover {
    transform: scale(1.1) translateZ(0);
}

/* 关键帧动画 */
@keyframes slideIn {
    from {
        transform: translateX(-100%);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

.slide-in {
    animation: slideIn 0.5s ease forwards;
}

/* 高性能动画属性 */
.optimized-animation {
    /* 这些属性由GPU加速 */
    transform: translate3d(0, 0, 0);
    opacity: 1;

    /* 这些属性性能较差,避免在动画中使用 */
    /* margin, padding, width, height, top, left */
}
                            

// ❌ 低效 - 使用jQuery动画改变布局属性
$("#element").animate({
    width: "200px",
    height: "100px",
    marginLeft: "50px"
}, 500);

// ✅ 高效 - 使用CSS3 transform
$("#element").addClass("expanded");

// CSS
.expanded {
    transform: scale(1.2) translateX(50px);
    transition: transform 0.5s ease;
}

// ✅ 混合使用 - jQuery控制CSS3动画
$("#element").css({
    transform: "translateX(100px)",
    transition: "transform 0.5s ease"
});

// 检测动画完成
$("#element").on("transitionend", function() {
    console.log("CSS3动画完成");
});

// 性能对比
function testAnimationPerformance() {
    const $element = $("#test-element");

    // jQuery动画
    console.time('jQuery动画');
    $element.animate({left: "500px"}, 1000, function() {
        console.timeEnd('jQuery动画');
    });

    // CSS3动画
    console.time('CSS3动画');
    $element.addClass("move-right");
    $element.on("transitionend", function() {
        console.timeEnd('CSS3动画');
        $(this).off("transitionend");
    });
}
                            

4.2 优化jQuery动画


// 1. 使用stop()防止动画队列堆积
$("#element").hover(
    function() {
        $(this).stop().animate({height: "200px"}, 300);
    },
    function() {
        $(this).stop().animate({height: "100px"}, 300);
    }
);

// 2. 清除动画队列
$("#element").stop(true, true).animate({/* ... */});

// 3. 使用show/hide代替高度动画
// ❌ 低效
$("#element").animate({height: "toggle"}, 300);

// ✅ 高效
$("#element").slideToggle(300);
// 或
$("#element").toggle(300);

// 4. 批量动画
// ❌ 低效 - 串行动画
$("#element")
    .animate({width: "200px"}, 300)
    .animate({height: "200px"}, 300)
    .animate({opacity: 0.5}, 300);

// ✅ 高效 - 并行动画
$("#element").animate({
    width: "200px",
    height: "200px",
    opacity: 0.5
}, 300);

// 5. 使用缓动函数
$("#element").animate({
    left: "500px"
}, {
    duration: 1000,
    easing: "easeOutExpo" // 使用缓动函数
});

// 6. 限制动画频率
let isAnimating = false;
$("#trigger").click(function() {
    if(!isAnimating) {
        isAnimating = true;
        $("#element").animate({/* ... */}, 300, function() {
            isAnimating = false;
        });
    }
});
                            

4.3 使用requestAnimationFrame


// 传统setTimeout动画的问题
function animateWithTimeout() {
    const element = $("#element")[0];
    let position = 0;

    function step() {
        position += 2;
        element.style.left = position + "px";

        if(position < 500) {
            setTimeout(step, 16); // 约60fps
        }
    }

    step();
}

// 使用requestAnimationFrame(推荐)
function animateWithRAF() {
    const element = $("#element")[0];
    let position = 0;
    let lastTime = 0;

    function step(timestamp) {
        if(!lastTime) lastTime = timestamp;
        const delta = timestamp - lastTime;

        // 基于时间差计算移动距离
        position += (delta / 16) * 2; // 60fps基准
        element.style.left = Math.min(position, 500) + "px";

        if(position < 500) {
            requestAnimationFrame(step);
        }
    }

    requestAnimationFrame(step);
}

// jQuery动画与RAF结合
$.fn.animateWithRAF = function(properties, duration) {
    const $element = this;
    const startTime = performance.now();
    const startProps = {};

    // 记录起始值
    Object.keys(properties).forEach(key => {
        startProps[key] = parseFloat($element.css(key)) || 0;
    });

    function animate(currentTime) {
        const elapsed = currentTime - startTime;
        const progress = Math.min(elapsed / duration, 1);

        // 计算当前值
        const currentProps = {};
        Object.keys(properties).forEach(key => {
            const start = startProps[key];
            const end = parseFloat(properties[key]);
            currentProps[key] = start + (end - start) * progress;
        });

        // 应用样式
        $element.css(currentProps);

        if(progress < 1) {
            requestAnimationFrame(animate);
        }
    }

    requestAnimationFrame(animate);
    return this;
};

// 使用自定义动画
$("#element").animateWithRAF({
    left: "500px",
    opacity: 0.5
}, 1000);
                            

5. AJAX优化

AJAX请求优化可以减少网络开销,提升用户体验。

5.1 请求合并与缓存


// ❌ 低效 - 多个独立请求
function loadUserData(userId) {
    $.get("/api/user/" + userId, function(user) {
        // ...
    });

    $.get("/api/user/" + userId + "/posts", function(posts) {
        // ...
    });

    $.get("/api/user/" + userId + "/friends", function(friends) {
        // ...
    });
}

// ✅ 高效 - 合并请求
function loadUserData(userId) {
    $.when(
        $.get("/api/user/" + userId),
        $.get("/api/user/" + userId + "/posts"),
        $.get("/api/user/" + userId + "/friends")
    ).then(function(userResponse, postsResponse, friendsResponse) {
        const user = userResponse[0];
        const posts = postsResponse[0];
        const friends = friendsResponse[0];
        // 统一处理
    });
}

// ✅ 更高效 - 批量接口
function loadUserData(userId) {
    $.get("/api/user-data/" + userId, function(data) {
        // data包含用户、帖子、好友等所有信息
        const {user, posts, friends} = data;
    });
}

// 请求缓存
const requestCache = {};

function cachedRequest(url, callback) {
    // 检查缓存
    if(requestCache[url]) {
        callback(requestCache[url]);
        return;
    }

    // 发送请求
    $.get(url, function(data) {
        // 缓存结果
        requestCache[url] = data;
        callback(data);
    });
}

// 带过期时间的缓存
const cacheWithExpiry = {
    data: {},

    set: function(key, value, ttl = 60000) { // 默认1分钟
        this.data[key] = {
            value: value,
            expiry: Date.now() + ttl
        };
    },

    get: function(key) {
        const item = this.data[key];
        if(!item) return null;

        if(Date.now() > item.expiry) {
            delete this.data[key];
            return null;
        }

        return item.value;
    }
};
                            

5.2 请求取消与超时


// 请求取消
let currentRequest = null;

function search(query) {
    // 取消之前的请求
    if(currentRequest) {
        currentRequest.abort();
    }

    currentRequest = $.ajax({
        url: "/api/search",
        data: {q: query},
        success: function(data) {
            displayResults(data);
            currentRequest = null;
        },
        error: function(xhr, status) {
            if(status !== "abort") {
                showError("搜索失败");
            }
            currentRequest = null;
        }
    });
}

// 请求超时
function fetchDataWithTimeout(url, timeout = 5000) {
    return $.ajax({
        url: url,
        timeout: timeout,
        beforeSend: function() {
            // 显示加载指示器
            $("#loading").show();
        },
        complete: function() {
            // 隐藏加载指示器
            $("#loading").hide();
        },
        error: function(xhr, status) {
            if(status === "timeout") {
                showError("请求超时,请重试");
            }
        }
    });
}

// 并发请求限制
class RequestQueue {
    constructor(maxConcurrent = 3) {
        this.maxConcurrent = maxConcurrent;
        this.queue = [];
        this.active = 0;
    }

    add(request) {
        return new Promise((resolve, reject) => {
            this.queue.push({
                request,
                resolve,
                reject
            });
            this.process();
        });
    }

    process() {
        if(this.active >= this.maxConcurrent || this.queue.length === 0) {
            return;
        }

        this.active++;
        const {request, resolve, reject} = this.queue.shift();

        $.ajax(request)
            .done(resolve)
            .fail(reject)
            .always(() => {
                this.active--;
                this.process();
            });
    }
}

// 使用请求队列
const requestQueue = new RequestQueue(2); // 最大并发2

// 添加请求到队列
requestQueue.add({
    url: "/api/data1"
}).then(data => {
    console.log("data1 loaded");
});

requestQueue.add({
    url: "/api/data2"
}).then(data => {
    console.log("data2 loaded");
});
                            

5.3 数据压缩与分页


// 数据压缩
function compressData(data) {
    // 移除不必要的数据
    const compressed = {
        items: data.items.map(item => ({
            id: item.id,
            name: item.name,
            // 只保留必要的字段
        }))
    };
    return compressed;
}

// 分页加载
class PaginatedLoader {
    constructor(url, pageSize = 20) {
        this.url = url;
        this.pageSize = pageSize;
        this.currentPage = 1;
        this.isLoading = false;
        this.hasMore = true;
    }

    loadNextPage() {
        if(this.isLoading || !this.hasMore) {
            return Promise.resolve([]);
        }

        this.isLoading = true;

        return $.ajax({
            url: this.url,
            data: {
                page: this.currentPage,
                pageSize: this.pageSize
            }
        }).then(data => {
            this.isLoading = false;

            if(data.items.length < this.pageSize) {
                this.hasMore = false;
            } else {
                this.currentPage++;
            }

            return data.items;
        }).catch(() => {
            this.isLoading = false;
            return [];
        });
    }

    reset() {
        this.currentPage = 1;
        this.hasMore = true;
        this.isLoading = false;
    }
}

// 使用分页加载器
const userLoader = new PaginatedLoader("/api/users", 10);

function loadMoreUsers() {
    userLoader.loadNextPage().then(users => {
        appendUsers(users);
        if(!userLoader.hasMore) {
            $("#load-more").hide();
        }
    });
}

// 无限滚动
$(window).on("scroll", throttle(function() {
    const scrollBottom = $(window).scrollTop() + $(window).height();
    const documentHeight = $(document).height();

    if(documentHeight - scrollBottom < 500) { // 接近底部
        loadMoreUsers();
    }
}, 200));
                            

6. 代码结构与压缩

良好的代码结构和压缩可以减少文件大小,提高加载速度。

6.1 模块化与代码组织


// 立即执行函数表达式 (IIFE) - 避免污染全局命名空间
(function($, window, document) {
    'use strict';

    // 私有变量
    const defaults = {
        color: 'red',
        size: 'medium'
    };

    // 私有函数
    function privateHelper() {
        // 内部使用的函数
    }

    // 公共接口
    $.fn.myOptimizedPlugin = function(options) {
        const settings = $.extend({}, defaults, options);

        return this.each(function() {
            // 插件实现
        });
    };

})(jQuery, window, document);

// 模块模式
const MyModule = (function() {
    // 私有变量
    let counter = 0;

    // 私有函数
    function increment() {
        counter++;
    }

    // 公共接口
    return {
        getCount: function() {
            return counter;
        },

        incrementAndGet: function() {
            increment();
            return counter;
        }
    };
})();

// 使用
console.log(MyModule.getCount()); // 0
MyModule.incrementAndGet(); // 1

// 基于类的组织
class Widget {
    constructor(element, options) {
        this.$element = $(element);
        this.options = options;
        this.init();
    }

    init() {
        // 初始化
        this.bindEvents();
    }

    bindEvents() {
        // 事件绑定
        this.$element.on('click', this.handleClick.bind(this));
    }

    handleClick(event) {
        // 事件处理
    }

    destroy() {
        // 清理
        this.$element.off('click');
    }
}
                            

6.2 代码压缩与构建


// package.json 示例
{
  "name": "my-jquery-app",
  "version": "1.0.0",
  "scripts": {
    "build": "npm run lint && npm run bundle && npm run minify",
    "lint": "eslint src/**/*.js",
    "bundle": "browserify src/main.js -o dist/bundle.js",
    "minify": "uglifyjs dist/bundle.js -o dist/bundle.min.js -c -m",
    "dev": "watchify src/main.js -o dist/bundle.js -v"
  },
  "devDependencies": {
    "browserify": "^17.0.0",
    "uglify-js": "^3.14.0",
    "eslint": "^8.0.0",
    "watchify": "^4.0.0"
  }
}

// webpack配置示例 (webpack.config.js)
module.exports = {
    mode: 'production', // 或 'development'
    entry: './src/index.js',
    output: {
        filename: 'bundle.min.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }
        ]
    },
    optimization: {
        minimize: true,
        minimizer: [
            new TerserPlugin({
                terserOptions: {
                    compress: {
                        drop_console: true, // 移除console.log
                        drop_debugger: true // 移除debugger
                    }
                }
            })
        ]
    }
};

// Grunt配置示例 (Gruntfile.js)
module.exports = function(grunt) {
    grunt.initConfig({
        uglify: {
            options: {
                mangle: true,
                compress: true,
                sourceMap: true
            },
            target: {
                files: {
                    'dist/app.min.js': ['src/**/*.js']
                }
            }
        },
        cssmin: {
            target: {
                files: {
                    'dist/style.min.css': ['src/**/*.css']
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-cssmin');

    grunt.registerTask('default', ['uglify', 'cssmin']);
};
                            

6.3 懒加载与按需加载


// 图片懒加载
class LazyLoader {
    constructor() {
        this.images = [];
        this.init();
    }

    init() {
        // 收集所有需要懒加载的图片
        this.images = $('img[data-src]').toArray();

        // 监听滚动事件(节流)
        $(window).on('scroll', throttle(this.checkImages.bind(this), 100));

        // 初始检查
        this.checkImages();
    }

    checkImages() {
        const windowHeight = $(window).height();
        const scrollTop = $(window).scrollTop();

        this.images = this.images.filter(img => {
            const $img = $(img);
            const offsetTop = $img.offset().top;

            // 如果图片进入视口
            if(offsetTop < scrollTop + windowHeight + 100) { // 提前100px加载
                this.loadImage($img);
                return false; // 从数组中移除
            }

            return true; // 保留在数组中
        });

        // 如果所有图片都加载完成,移除事件监听
        if(this.images.length === 0) {
            $(window).off('scroll');
        }
    }

    loadImage($img) {
        const src = $img.data('src');
        if(!src) return;

        const image = new Image();
        image.onload = () => {
            $img.attr('src', src).removeAttr('data-src');
            $img.addClass('loaded');
        };
        image.src = src;
    }
}

// 初始化懒加载
new LazyLoader();

// 组件懒加载
function loadComponent(componentName) {
    // 检查是否已加载
    if(window[componentName]) {
        return Promise.resolve(window[componentName]);
    }

    // 动态加载
    return $.getScript(`/components/${componentName}.js`)
        .then(() => {
            return window[componentName];
        });
}

// 使用
$('#open-modal').click(function() {
    loadComponent('Modal').then(Modal => {
        const modal = new Modal();
        modal.open();
    });
});

// 路由级懒加载
const routes = {
    '/dashboard': () => import('./modules/dashboard.js'),
    '/users': () => import('./modules/users.js'),
    '/settings': () => import('./modules/settings.js')
};

function loadRoute(route) {
    if(routes[route]) {
        routes[route]().then(module => {
            module.init();
        });
    }
}

// 监听路由变化
$(window).on('hashchange', function() {
    const route = location.hash.slice(1) || '/dashboard';
    loadRoute(route);
});
                            
性能优化检查清单:
  • ✓ 选择器是否高效?(优先ID,避免*)
  • ✓ DOM操作是否批量处理?
  • ✓ 是否使用了事件委托?
  • ✓ 频繁触发的事件是否节流/防抖?
  • ✓ 动画是否优先使用CSS3?
  • ✓ AJAX请求是否合并和缓存?
  • ✓ 代码是否压缩和合并?
  • ✓ 图片和组件是否懒加载?
  • ✓ 是否移除不必要的console.log?
  • ✓ 第三方库是否使用CDN?

jQuery性能优化速查表

优化领域 优化技巧 性能提升
选择器 缓存选择器、优先ID、避免* 减少DOM查询50-80%
DOM操作 批量操作、DocumentFragment、离线操作 减少重排重绘70%
事件处理 事件委托、节流防抖、命名空间 减少内存占用60%
动画 CSS3动画、requestAnimationFrame、stop() 提升FPS到60
AJAX 请求合并、缓存、取消、分页 减少请求数50%
代码 模块化、压缩、懒加载、CDN 减少文件大小70%

实战练习:优化现有代码

请优化以下低效的jQuery代码:

原始代码(需要优化):

// 低效代码示例
function inefficientCode() {
    // 1. 选择器问题
    for(let i = 0; i < 100; i++) {
        $(".item").css("color", "red");
    }

    // 2. DOM操作问题
    for(let i = 0; i < 50; i++) {
        $("#container").append("
Item " + i + "
"); } // 3. 事件处理问题 $(".btn").click(function() { // 处理点击 }); // 4. 动画问题 $(".box").hover(function() { $(this).animate({width: "200px"}, 300); }, function() { $(this).animate({width: "100px"}, 300); }); }
优化建议:
优化结果将显示在这里...