jQuery 效能優化
雖然 jQuery 讓開發更加便利,但不當的使用方式可能會導致效能問題。本篇介紹如何撰寫更有效率的 jQuery 程式碼。
選擇器優化
ID 選擇器最快
ID 選擇器直接對應到原生的 document.getElementById(),是最快的選擇方式:
// 最快
$('#content');
// 不必要的限定,反而更慢
$('div#content');
避免萬用選擇器
萬用選擇器會遍歷所有元素,效能很差:
// 避免
$('*');
$('.container > *');
// 改用具體的選擇器
$('.container > div');
從右到左解析
瀏覽器解析 CSS 選擇器是從右到左,因此右邊的選擇器越具體越好:
// 較慢 - 先找所有 a,再過濾
$('.menu a');
// 較快 - 先用 ID 縮小範圍
$('#menu a');
// 更好 - 給連結加上 class
$('.menu-link');
縮小搜尋範圍
使用 find() 或第二個參數來縮小搜尋範圍:
// 搜尋整個文件
$('.item');
// 只在特定範圍內搜尋
$('#container').find('.item');
// 或使用第二個參數(context)
$('.item', '#container');
// 如果已經有 jQuery 物件,使用 find() 更好
var $container = $('#container');
$container.find('.item');
避免過度具體
過度具體的選擇器反而更慢且難以維護:
// 避免 - 過度具體
$('div.container ul.list li.item a.link');
// 改用
$('.item .link');
// 或直接
$('.item-link');
快取 jQuery 物件
避免重複選取
每次使用 $() 都會執行 DOM 查詢,應該將結果快取起來:
// 不好 - 重複查詢
$('.item').addClass('active');
$('.item').css('color', 'red');
$('.item').fadeIn();
// 好 - 快取結果
var $items = $('.item');
$items.addClass('active');
$items.css('color', 'red');
$items.fadeIn();
// 更好 - 使用鏈式呼叫
$('.item').addClass('active').css('color', 'red').fadeIn();
命名慣例
使用 $ 前綴表示 jQuery 物件:
var $header = $('#header');
var $items = $('.item');
var $submitBtn = $('#submit');
// 這樣可以一眼看出是 jQuery 物件
$header.addClass('fixed');
在閉包中快取
(function () {
// 快取常用元素
var $window = $(window);
var $document = $(document);
var $body = $('body');
$window.on('scroll', function () {
// 使用快取的 $window
var scrollTop = $window.scrollTop();
// ...
});
})();
DOM 操作優化
批量操作
避免在迴圈中逐一操作 DOM,應該先組合好再一次插入:
// 不好 - 每次迴圈都操作 DOM
var $list = $('#list');
for (var i = 0; i < 100; i++) {
$list.append('<li>項目 ' + i + '</li>');
}
// 好 - 先組合字串,再一次插入
var html = '';
for (var i = 0; i < 100; i++) {
html += '<li>項目 ' + i + '</li>';
}
$('#list').append(html);
// 或使用陣列
var items = [];
for (var i = 0; i < 100; i++) {
items.push('<li>項目 ' + i + '</li>');
}
$('#list').append(items.join(''));
使用 DocumentFragment
var fragment = document.createDocumentFragment();
for (var i = 0; i < 100; i++) {
var li = document.createElement('li');
li.textContent = '項目 ' + i;
fragment.appendChild(li);
}
$('#list').append(fragment);
使用 detach() 減少 reflow
大量操作元素時,先將元素從 DOM 移除,操作完再放回:
var $list = $('#list');
var $parent = $list.parent();
// 從 DOM 移除(但保留資料和事件)
$list.detach();
// 進行大量操作
for (var i = 0; i < 1000; i++) {
$list.append('<li>項目 ' + i + '</li>');
}
// 放回 DOM
$parent.append($list);
減少 reflow 和 repaint
// 不好 - 多次讀寫交替會導致多次 reflow
var $box = $('#box');
$box.css('width', '100px');
var height = $box.height(); // 強制 reflow
$box.css('height', height + 'px');
// 好 - 先讀取,再寫入
var $box = $('#box');
var height = $box.height(); // 讀取
$box.css({
// 一次寫入
width: '100px',
height: height + 'px',
});
使用 CSS 類別而非行內樣式
// 不好 - 多次修改行內樣式
$('#box').css('color', 'red').css('font-size', '20px').css('background', 'yellow');
// 好 - 預先定義 CSS 類別
$('#box').addClass('highlight');
事件優化
使用事件委派
對於大量相似元素或動態新增的元素,使用事件委派:
// 不好 - 為每個元素綁定事件
$('.item').on('click', function () {
// ...
});
// 好 - 事件委派
$('#container').on('click', '.item', function () {
// ...
});
事件委派的好處:
- 只需要一個事件處理函式
- 動態新增的元素自動有效
- 減少記憶體使用
適時解除綁定
不再需要的事件應該解除綁定:
// 使用命名空間方便解除
$('#btn').on('click.myFeature', handler);
// 之後解除
$('#btn').off('.myFeature');
// 元素移除前解除事件
$('#element').off().remove();
節流和防抖
對於高頻率觸發的事件(如 scroll、resize、input),使用節流或防抖:
// 節流 - 固定間隔執行一次
function throttle(fn, delay) {
var lastCall = 0;
return function () {
var now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn.apply(this, arguments);
}
};
}
// 防抖 - 停止觸發後才執行
function debounce(fn, delay) {
var timer;
return function () {
var context = this;
var args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
}
// 使用
$(window).on('scroll', throttle(handleScroll, 100));
$('#search').on('input', debounce(handleSearch, 300));
使用 one() 處理一次性事件
// 只執行一次
$('#btn').one('click', function () {
// 初始化操作
});
動畫優化
CSS 動畫優於 jQuery 動畫
CSS 動畫由瀏覽器優化,效能更好:
.fade-in {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
// 使用 CSS 動畫
$('#box').addClass('fade-in');
// 而非 jQuery 動畫
$('#box').fadeIn(300);
使用 stop() 防止動畫堆疊
// 避免動畫堆疊
$('#box').stop().animate({ left: '100px' }, 300);
// hover 時特別重要
$('.item').hover(
function () {
$(this).stop().animate({ opacity: 1 }, 200);
},
function () {
$(this).stop().animate({ opacity: 0.5 }, 200);
}
);
避免同時動畫太多元素
// 不好 - 同時動畫 1000 個元素
$('.item').fadeIn(300);
// 好 - 分批或延遲
$('.item').each(function (index) {
$(this)
.delay(index * 50)
.fadeIn(300);
});
關閉動畫(測試或效能考量)
// 全域關閉動畫
$.fx.off = true;
// 根據使用者偏好
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
$.fx.off = true;
}
Ajax 優化
快取 Ajax 結果
var cache = {};
function getData(url) {
if (cache[url]) {
return $.Deferred().resolve(cache[url]).promise();
}
return $.get(url).then(function (data) {
cache[url] = data;
return data;
});
}
取消未完成的請求
var currentRequest = null;
$('#search').on('input', function () {
var query = $(this).val();
// 取消之前的請求
if (currentRequest) {
currentRequest.abort();
}
currentRequest = $.get('/api/search', { q: query })
.done(function (data) {
// 處理結果
})
.always(function () {
currentRequest = null;
});
});
其他優化建議
使用最新版 jQuery
新版本通常包含效能改進和 bug 修復。
使用壓縮版
生產環境使用 .min.js 版本:
<!-- 開發環境 -->
<script src="jquery.js"></script>
<!-- 生產環境 -->
<script src="jquery.min.js"></script>
使用 Slim 版本
如果不需要 Ajax 和動畫效果,可以使用 Slim 版本減少檔案大小:
<script src="jquery.slim.min.js"></script>
延遲載入非必要的腳本
<!-- 非必要的腳本放在頁面底部或使用 defer -->
<script src="jquery.min.js"></script>
<script src="app.js" defer></script>
避免使用 $.each() 處理大型陣列
原生迴圈更快:
var items = [
/* 大量資料 */
];
// 較慢
$.each(items, function (index, item) {
// ...
});
// 較快
for (var i = 0, len = items.length; i < len; i++) {
var item = items[i];
// ...
}
// 或使用 forEach
items.forEach(function (item, index) {
// ...
});
使用效能分析工具
- 瀏覽器開發者工具的 Performance 面板
- Console 的
console.time()和console.timeEnd()
console.time('選擇器測試');
for (var i = 0; i < 1000; i++) {
$('#element');
}
console.timeEnd('選擇器測試');
效能檢查清單
| 項目 | 建議 |
|---|---|
| 選擇器 | 使用 ID 選擇器,避免萬用選擇器 |
| 快取 | 快取重複使用的 jQuery 物件 |
| DOM 操作 | 批量操作,減少 reflow |
| 事件 | 使用事件委派,適時解除綁定 |
| 動畫 | 優先使用 CSS 動畫,使用 stop() |
| Ajax | 快取結果,取消重複請求 |
| 載入 | 使用壓縮版,延遲載入 |