9.1 イベントデリゲーション
JavaScriptでのイベント処理は、パフォーマンスの最適化やアプリのアーキテクチャ向上のために複雑な管理を要することがあるよ。
JavaScriptでのイベント処理の最適化は、特に大量の要素を扱うときに、パフォーマンスの高いWebアプリケーションを作成するために非常に重要なんだ。この文脈で、イベントデリゲーションや不要なハンドラーの防止が重要な役割を果たすよ。
イベントデリゲーション は、イベントハンドラーをそれぞれの子要素ではなく、親要素に設定するパターンなんだ。これにより、多くの子要素に対して効率的にイベントを管理することができる。要は、親に対して1回だけ呼び出されて、それぞれの要素では呼び出されないんだ。
イベントデリゲーション なし の例:
<!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 を使う: イベントハンドラーを1回だけ動かしたいなら、{ 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. デバウンシングとスロットリング: これらのテクニックは、scroll
やresize
のように頻繁に呼び出されるイベントハンドラーを最適化するのに役立つんだ。
9.3 デバウンシング
デバウンシングは、連続した関数呼び出しを1つにまとめ、イベントが終了した後でのみ実行されるよ。
例:
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 スロットリング
トローリング スロットリングは、関数が指定された時間間隔より頻繁に呼び出されないように保証するよ。
例:
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