9.1 事件委派
在JavaScript中處理事件可能需要複雜的管理來優化性能和改進應用程序架構。
在JavaScript中優化事件處理對於創建高性能的Web應用程序至關重要,特別是在需要處理大量元素時。在這個背景下,事件委派和防止不必要的處理器起著關鍵作用。
事件委派 是一種模式,其中事件處理器設置在父元素上,而不是每個單獨的子元素上。這允許對大量子元素的事件進行有效管理,因為處理器只為父元素調用一次,而不是每個元素都調用。
沒有事件委派的例子:
<!DOCTYPE html>
<html>
<head>
<title>事件委派例子</title>
</head>
<body>
<ul id="list">
<li>元素 1</li>
<li>元素 2</li>
<li>元素 3</li>
</ul>
<script>
const list = document.querySelector('ul');
const items = document.querySelectorAll('#list li');
items.forEach(item => {
item.addEventListener('click', function(event) {
const li = document.createElement('li');
li.innerText = "新的元素, 事件不會在上面觸發";
list.appendChild(li);
alert(`你點擊了: ${event.target.textContent}`);
});
});
</script>
</body>
</html>
問題: 如果列表元素是動態增加的,則需要為每個新元素添加處理程序。
解決方案 —— 事件委派的例子:
<!DOCTYPE html>
<html>
<head>
<title>事件委派例子</title>
</head>
<body>
<ul id="list">
<li>元素 1</li>
<li>元素 2</li>
<li>元素 3</li>
</ul>
<script>
const list = document.getElementById('list');
list.addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
const li = document.createElement('li');
li.innerText = "新的元素, 事件會在上面觸發";
list.appendChild(li);
alert(`你點擊了: ${event.target.textContent}`);
}
});
</script>
</body>
</html>
優勢:
- 處理程序僅添加到父元素上
- 動態添加的元素將自動支持事件處理程序
9.2 防止不必要的處理程序
為了避免性能下降,重要的是要最小化事件處理程序的數量,特別是如果它們附加到多個元素或經常調用的話。幾種優化方法:
1. 減少處理程序的數量: 使用事件委派來減少處理程序的數量。
2. 在 addEventListener 中使用 once: 如果事件處理程序只需觸發一次,請在添加處理程序時使用選項 { once: true }
。
例子:
<!DOCTYPE html>
<html>
<head>
<title>單次事件例子</title>
</head>
<body>
<button id="myButton">點擊我!</button>
<script>
const button = document.getElementById('myButton');
button.addEventListener('click', function(event) {
alert('你點擊了按鈕');
}, { once: true });
</script>
</body>
</html>
3. 防抖(debouncing) 和 節流(throttling): 這些技術對於優化頻繁調用的事件處理程序非常有用,如 scroll
或 resize
。
9.3 防抖(debouncing)
防抖(debouncing) 將多個連續的函數調用合併為一個,並且只在事件流停止後執行。
例子:
function debounce(func, wait) {
let timeout;
return function(...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
window.addEventListener('resize', debounce(() => {
console.log('Window resized');
}, 300));
試著改變窗口寬度以查看結果
防抖例子
在這個例子中,搜索函數只有在用戶停止輸入文本300毫秒後才會被調用。
<!DOCTYPE html>
<html>
<head>
<title>防抖例子</title>
</head>
<body>
<div style="min-height: 55px">
<input type="text" id="searchInput" placeholder="開始輸入搜索...">
<div id="results"></div>
</div>
<script>
const searchInput = document.getElementById('searchInput');
const results = document.getElementById('results');
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
function performSearch(event) {
const query = event.target.value;
results.textContent = '搜尋: ' + query;
// 模擬服務器請求
setTimeout(() => {
results.textContent = '你搜尋了: ' + query;
}, 500);
}
const debouncedSearch = debounce(performSearch, 300);
searchInput.addEventListener('input', debouncedSearch);
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>防抖例子</title>
</head>
<body>
<input type="text" id="searchInput" placeholder="開始輸入搜索...">
<div id="results"></div>
<script>
const searchInput = document.getElementById('searchInput');
const results = document.getElementById('results');
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
function performSearch(event) {
const query = event.target.value;
results.textContent = 'Searching for: ' + query;
// 模擬服務器請求
setTimeout(() => {
results.textContent = '你搜尋了: ' + query;
}, 500);
}
const debouncedSearch = debounce(performSearch, 300);
searchInput.addEventListener('input', debouncedSearch);
</script>
</body>
</html>
9.4 節流(throttling)
拖車節流(throttling) 確保函數不會被頻繁地調用,限制在指定的時間間隔內最多調用一次。
例子:
function throttle(func, limit) {
let inThrottle;
return function(...args) {
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
window.addEventListener('scroll', throttle(() => {
console.log('Window scrolled');
}, 300));
另一個節流的例子
在這個例子中,滾動處理程序的調用頻率不超過每200毫秒一次,這有助於防止在頻繁滾動的事件中使瀏覽器過載。
<!DOCTYPE html>
<html>
<head>
<title>節流例子</title>
</head>
<body>
<div id="scrollContainer" style="height: 200px; overflow-y: scroll;">
<div style="height: 1000px;"></div>
</div>
<div id="status">開始滾動以查看效果</div>
<script>
const scrollContainer = document.getElementById('scrollContainer');
const status = document.getElementById('status');
function throttle(func, limit) {
let lastFunc;
let lastRan;
return function(...args) {
const context = this;
if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
if ((Date.now() - lastRan) >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
function handleScroll(event) {
status.textContent = '滾動中... ' + new Date().toLocaleTimeString();
}
const throttledScroll = throttle(handleScroll, 200);
scrollContainer.addEventListener('scroll', throttledScroll);
</script>
</body>
</html>
9.5 使用被動監聽器優化事件
被動事件監聽器 (passive event listeners) 用於提升性能,特別是在處理滾動(scroll
)事件時。當事件處理器設置為被動時,表示它不會調用preventDefault()
,這允許瀏覽器優化性能。
例子:
window.addEventListener('scroll', function(event) {
console.log('Scrolling');
}, { passive: true });
GO TO FULL VERSION