2026年7月に内容を更新しました。
現在のWeb APIとjQuery 4.0を前提に、コード例、イベント委譲、Fetch API、
アニメーションの説明を見直しました。
記事の主旨は変えず、jQueryからネイティブJavaScriptへ移行する際の
注意点が伝わりやすくなるように修正しています。
はじめに
私は長い間、レガシーコードを扱ってきました。その中で、jQueryを残すべきか、ネイティブJavaScriptへ移行すべきかという話題には何度も向き合っています。
jQueryは現在も有用なライブラリです。特に、既存システム、jQueryプラグインへ強く依存する画面、古いブラウザを対象とする環境では、無理に削除する必要はありません。
一方、新しく作るWebアプリケーションでは、jQueryが担っていた用途の多くを、次のWeb Platform APIで実装できます。
- DOM API
addEventListener()- Fetch API
classList- CSS Transition
CustomEvent
これらはECMAScriptだけに含まれる機能ではありません。DOM Standard、Fetch Standard、HTML、CSSなど、複数のWeb標準によって提供されています。
この記事では、よく使われるjQueryの処理を、ネイティブJavaScriptへ置き換える方法をまとめます。
jQueryとネイティブJavaScriptでは、戻り値や対象件数が異なる場合があります。
特に、jQueryのセレクタは複数要素をまとめて扱いますが、querySelector()は最初の1件だけを返します。
対応表
| 用途 | jQuery | ネイティブJavaScript |
|---|---|---|
| 最初の要素を取得 | $('.item').first() |
document.querySelector('.item') |
| すべての要素を取得 | $('.item') |
document.querySelectorAll('.item') |
| 子孫要素を取得 | $container.find('.item') |
container.querySelectorAll('.item') |
| イベント登録 | .on('click', handler) |
.addEventListener('click', handler) |
| クラス追加 | .addClass('active') |
.classList.add('active') |
| 要素を隠す | .hide() |
element.hidden = true |
| 要素を表示 | .show() |
element.hidden = false |
| HTTPリクエスト | $.ajax() |
fetch() |
| 要素作成 | $('<div>') |
document.createElement('div') |
要素を選択する
最初の1件を取得する
// jQuery
const $item = $('.item').first();
// JavaScript
const item = document.querySelector('.item');
querySelector()は、条件に一致する最初の要素を返します。見つからない場合はnullです。
const item = document.querySelector('.item');
if (!item) {
return;
}
item.textContent = '更新しました';
すべての要素を取得する
// jQuery
const $items = $('.item');
// JavaScript
const items = document.querySelectorAll('.item');
querySelectorAll()は、静的なNodeListを返します。
document.querySelectorAll('.item').forEach((item) => {
item.classList.add('active');
});
IDや属性で取得する
const itemById = document.getElementById('item');
const itemBySelector = document.querySelector('#item');
const externalLinks = document.querySelectorAll('a[target="_blank"]');
属性値は引用符で囲むと、安全で読みやすくなります。
特定の要素内を検索する
jQueryの.find()は、条件に一致するすべての子孫要素を返します。
同じ挙動に近づける場合、querySelectorAll()を使います。
// jQuery
const $container = $('.container');
const $items = $container.find('.item');
// JavaScript
const container = document.querySelector('.container');
const items = container?.querySelectorAll('.item') ?? [];
最初の1件だけが必要な場合は、querySelector()を使います。
const firstItem = container?.querySelector('.item') ?? null;
複数の要素へ処理を適用する
jQueryは、選択した複数要素へ同じ処理をまとめて適用できます。
// jQuery
$('.item').addClass('active');
ネイティブJavaScriptでは、対象を反復処理します。
document.querySelectorAll('.item').forEach((item) => {
item.classList.add('active');
});
共通処理が多い場合は、関数として切り出せます。
const forEachElement = (selector, callback) => {
document.querySelectorAll(selector).forEach(callback);
};
forEachElement('.item', (item) => {
item.classList.add('active');
});
親要素や隣接要素へ移動する
// jQuery
const $nextItem = $('.item').first().next();
const $previousItem = $('.item').first().prev();
const $parent = $('.item').first().parent();
// JavaScript
const item = document.querySelector('.item');
const nextItem = item?.nextElementSibling ?? null;
const previousItem = item?.previousElementSibling ?? null;
const parent = item?.parentElement ?? null;
条件に一致する最も近い祖先を取得する場合は、closest()を使います。
const button = document.querySelector('.button');
const form = button?.closest('form') ?? null;
イベントを登録する
// jQuery
$('.button').on('click', handleClick);
// JavaScript
const button = document.querySelector('.button');
button?.addEventListener('click', handleClick);
複数の要素へ登録する場合は、反復処理します。
document.querySelectorAll('.button').forEach((button) => {
button.addEventListener('click', handleClick);
});
リスナーを後で解除する場合は、同じ関数参照が必要です。
const handleClick = (event) => {
console.log(event.currentTarget);
};
button?.addEventListener('click', handleClick);
button?.removeEventListener('click', handleClick);
イベント委譲を使う
動的に追加される要素へイベントを適用する場合は、親要素へリスナーを登録します。
// jQuery
$('.container').on('click', '.active', handleClick);
ネイティブJavaScriptでは、イベントバブリングとclosest()を使います。
const container = document.querySelector('.container');
container?.addEventListener('click', (event) => {
const clickedElement =
event.target instanceof Element ? event.target.closest('.active') : null;
if (!clickedElement || !container.contains(clickedElement)) {
return;
}
handleClick(event, clickedElement);
});
event.target.matches('.active')だけでは、.active内のspanやアイコンをクリックした場合に検出できません。
closest()を使うと、クリックされた要素から祖先をたどって対象を探せます。
カスタムイベントを作成する
データを伴う独自イベントには、CustomEventを使います。
const item = document.querySelector('.item');
const event = new CustomEvent('item:updated', {
bubbles: true,
detail: {
itemId: 'example-item',
},
});
item?.dispatchEvent(event);
受信側では、detailからデータを取得できます。
document.addEventListener('item:updated', (event) => {
if (!(event instanceof CustomEvent)) {
return;
}
console.log(event.detail.itemId);
});
dispatchEvent()は、登録済みのリスナーを同期的に実行します。
クラスを操作する
// jQuery
$('.item').addClass('active');
$('.item').removeClass('active');
$('.item').toggleClass('active');
// JavaScript
const item = document.querySelector('.item');
item?.classList.add('active');
item?.classList.remove('active');
item?.classList.toggle('active');
複数のクラスもまとめて指定できます。
item?.classList.add('active', 'highlighted');
item?.classList.remove('active', 'highlighted');
クラスを置き換える場合は、replace()を使います。
item?.classList.replace('inactive', 'active');
クラスの有無は、contains()で確認できます。
if (item?.classList.contains('active')) {
console.log('activeです');
}
スタイルを変更する
最初の1件を変更する
const item = document.querySelector('.item');
if (item instanceof HTMLElement) {
item.style.color = '#000';
item.style.backgroundColor = 'red';
}
すべての要素を変更する
jQueryの.css()は、選択された全要素へ適用されます。
// jQuery
$('.item').css('color', '#000');
// JavaScript
document.querySelectorAll('.item').forEach((item) => {
if (item instanceof HTMLElement) {
item.style.color = '#000';
}
});
複数のスタイルをまとめて変更する場合は、Object.assign()も使えます。
if (item instanceof HTMLElement) {
Object.assign(item.style, {
color: '#000',
backgroundColor: 'red',
});
}
style.cssTextは既存のインラインスタイルを上書きするため、通常は個別プロパティの変更を推奨します。
要素を非表示・表示する
display: blockを直接設定すると、本来flexやgridだった要素の表示形式を壊す可能性があります。
単純な表示切り替えには、hidden属性が使えます。
const item = document.querySelector('.item');
if (item instanceof HTMLElement) {
item.hidden = true;
item.hidden = false;
}
CSSクラスで管理する方法もあります。
.is-hidden {
display: none;
}
item?.classList.add('is-hidden');
item?.classList.remove('is-hidden');
アプリケーションでは、表示状態をCSSクラスへ寄せる方が管理しやすい場合が多いです。
フェードイン・フェードアウトする
アニメーションは、JavaScriptで細かく制御するより、CSSへ責務を寄せると扱いやすくなります。
.fade-item {
opacity: 1;
transition: opacity 200ms ease;
}
.fade-item.is-hidden {
opacity: 0;
pointer-events: none;
}
const setVisible = (element, isVisible) => {
element.classList.toggle('is-hidden', !isVisible);
};
const item = document.querySelector('.fade-item');
if (item) {
setVisible(item, false);
setVisible(item, true);
}
完全にレイアウトから取り除きたい場合は、transitionend後にhiddenを設定します。
const fadeOut = (element) => {
const handleTransitionEnd = () => {
element.hidden = true;
element.removeEventListener('transitionend', handleTransitionEnd);
};
element.addEventListener('transitionend', handleTransitionEnd);
element.classList.add('is-hidden');
};
const fadeIn = (element) => {
element.hidden = false;
requestAnimationFrame(() => {
element.classList.remove('is-hidden');
});
};
transitionendは、対象プロパティの値が変化しない場合には発火しません。
実運用では、タイムアウトによるフォールバックやWeb Animations APIも検討してください。
DOMの準備完了を待つ
// jQuery
$(() => {
initialize();
});
通常のスクリプトでは、DOMContentLoadedを使えます。
document.addEventListener('DOMContentLoaded', () => {
initialize();
});
ただし、deferまたはtype="module"を指定したスクリプトは、DOMの解析後に実行されます。
<script type="module" src="/main.js"></script>
この場合、通常はそのまま初期化できます。
initialize();
動的に読み込まれるスクリプトにも対応する場合は、document.readyStateを確認します。
const runWhenReady = (callback) => {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', callback, {
once: true,
});
return;
}
callback();
};
runWhenReady(initialize);
HTTPリクエストを送信する
// jQuery
$.ajax({
url: 'data.json',
})
.done((data) => {
console.log(data);
})
.fail((error) => {
console.error(error);
});
Fetch APIでは、HTTPエラーを自動的に例外として扱いません。
response.okを確認し、必要な形式へ変換します。
const loadData = async () => {
const response = await fetch('data.json');
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json();
};
try {
const data = await loadData();
console.log(data);
} catch (error) {
console.error(error);
}
キャンセルが必要な場合は、AbortControllerを使います。
const controller = new AbortController();
const loadData = async () => {
const response = await fetch('data.json', {
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json();
};
controller.abort();
要素を作成する
// jQuery
const $element = $('<div>');
// JavaScript
const element = document.createElement('div');
属性やテキストも設定できます。
const element = document.createElement('div');
element.classList.add('item');
element.dataset.itemId = 'example-item';
element.textContent = '新しい要素';
DOMへ要素を追加する
// jQuery
$('.container').append($('<div>'));
// JavaScript
const container = document.querySelector('.container');
const element = document.createElement('div');
container?.append(element);
append()は、要素だけでなく文字列も追加できます。
container?.append('追加テキスト', element);
先頭へ追加する場合は、prepend()を使います。
container?.prepend(element);
テキストとHTMLを更新する
ユーザー入力や外部データを文字列として表示する場合は、textContentを使います。
// jQuery
$('.item').text('新しいテキスト');
// JavaScript
const item = document.querySelector('.item');
if (item) {
item.textContent = '新しいテキスト';
}
HTMLを挿入する場合、innerHTMLへ未検証の文字列を渡すとXSSにつながる可能性があります。
// 外部入力をそのまま入れない
item.innerHTML = externalInput;
単純なDOM構築では、createElement()とtextContentを組み合わせます。
const link = document.createElement('a');
link.href = '/items/example';
link.textContent = externalInput;
item?.replaceChildren(link);
jQueryを残した方がよいケース
ネイティブJavaScriptへ置き換えられるからといって、必ず移行すべきとは限りません。
次の条件では、jQueryを残す判断も合理的です。
- 安定稼働している既存システム
- jQueryプラグインへ強く依存している
- 移行コストに対する保守上の効果が小さい
- 対象ブラウザや組み込みWebViewに制約がある
- 段階的な改修が難しく、回帰リスクが高い
移行の目的は、jQueryを削除することではありません。
依存関係、バンドルサイズ、保守コスト、テスト容易性を改善できる場合に限り、価値があります。
段階的に移行するためのチェックリスト
- 対象ブラウザとWebViewを確認する
- jQueryプラグインへの依存を洗い出す
- セレクタが1件か複数件かを確認する
- イベント委譲の対象範囲を確認する
-
$.ajax()のエラー処理をfetch()へ移す - 表示制御をCSSクラスへ寄せる
- DOM操作部分にテストを追加する
- 小さな画面や機能単位で置き換える
- jQueryとネイティブ実装の共存期間を設ける
- 移行後に不要な依存を削除する
まとめ
現在のWeb Platformには、かつてjQueryが担っていた基本機能の多くが標準APIとして用意されています。
新規開発では、次の機能を中心にすれば、多くの処理を追加ライブラリなしで実装できます。
-
querySelector()とquerySelectorAll() addEventListener()classList- Fetch API
createElement()-
append()とreplaceChildren() - CSS Transition
CustomEvent
一方、既存システムからjQueryを削除すること自体を目的にしてはいけません。
対象件数、戻り値、イベント委譲、表示状態、HTTPエラーなどの差異を確認しながら、保守上の効果が得られる範囲から段階的に移行するのが安全です。
参考資料
- jQuery
- MDN Web Docs: Document.querySelector()
- MDN Web Docs: Document.querySelectorAll()
- MDN Web Docs: EventTarget.addEventListener()
- MDN Web Docs: Element.closest()
- MDN Web Docs: Fetch API
- MDN Web Docs: Response.ok
- MDN Web Docs: CustomEvent
- MDN Web Docs: DOMContentLoaded
- Baseline
- You Might Not Need jQuery