jQueryのDOM操作をVanilla JSで書き直す

「querySelector()って書き方は知ってるけど、その先どうすればいいの?」

「クラスの追加・削除はaddClass/removeClassが便利だったのに…」

「イベント処理って素のJavaScriptだと面倒なんじゃ…?」

前回は配列操作のモダンな書き方を学びました。今回は、jQueryの真骨頂とも言えるDOM操作について、モダンなJavaScriptでの書き方を見ていきましょう。

要素の取得

まずは要素の取得方法を比較してみましょう:

JS
// jQuery
const $button = $('#submitButton');
const $items = $('.item');
const $inputs = $('input[type="text"]');
const $firstItem = $('.item:first');
const $lastItem = $('.item:last');
const $form = $button.closest('form');

// Modern JavaScript
const button = document.querySelector('#submitButton');
const items = document.querySelectorAll('.item');
const inputs = document.querySelectorAll('input[type="text"]');
const firstItem = document.querySelector('.item');
const lastItem = document.querySelector('.item:last-child');
const form = button.closest('form');

主な違いと注意点:

  • querySelectorは最初の1つの要素、querySelectorAllは該当する全ての要素を返す
  • jQueryオブジェクトと異なり、直接DOM要素が取得できる
  • querySelectorAllの返り値は配列ではなくNodeListなので、必要に応じてArray.from()を使用する

要素の走査(要素の探索)

JS
// jQuery
const $parent = $element.parent();
const $children = $element.children();
const $next = $element.next();
const $prev = $element.prev();
const $siblings = $element.siblings();

// Modern JavaScript
const parent = element.parentElement;
const children = element.children;
const next = element.nextElementSibling;
const prev = element.previousElementSibling;
const siblings = [...parent.children].filter(child => child !== element);

ポイント:

  • ほとんどのDOM走査メソッドは直感的な名前で用意されている
  • 兄弟要素の取得など、一部の操作は自前で実装が必要
  • childrenHTMLCollectionを返すので、配列操作が必要な場合は変換が必要

クラスの操作

JS
// jQuery
$element.addClass('active');
$element.removeClass('disabled');
$element.toggleClass('selected');
$element.hasClass('visible');

// Modern JavaScript
element.classList.add('active');
element.classList.remove('disabled');
element.classList.toggle('selected');
element.classList.contains('visible');

// 複数のクラスを一度に操作
element.classList.add('active', 'visible');

classList APIのメリット:

  • メソッド名がjQueryと似ているため移行が容易
  • 複数クラスの一括操作が可能
  • パフォーマンスが優れている(DOMネイティブのAPI)

スタイルの操作

JS
// jQuery
$element.css('backgroundColor', '#f00');
$element.css({
    width: '100px',
    height: '100px',
    margin: '10px'
});

// Modern JavaScript
element.style.backgroundColor = '#f00';
Object.assign(element.style, {
    width: '100px',
    height: '100px',
    margin: '10px'
});

モダンJavaScriptでのスタイル操作のポイント:

  • キャメルケースでプロパティ名を指定(background-color → backgroundColor)
  • Object.assignを使うことで複数のスタイルを一括設定可能
  • CSSカスタムプロパティ(変数)も操作可能

要素の作成と追加

JS
// jQuery
const $newDiv = $('<div>')
    .addClass('container')
    .attr('id', 'newContent')
    .text('Hello World');
$('#parent').append($newDiv);

// Modern JavaScript: createElement方式
const newDiv = document.createElement('div');
newDiv.classList.add('container');
newDiv.id = 'newContent';
newDiv.textContent = 'Hello World';
document.querySelector('#parent').appendChild(newDiv);

// Modern JavaScript: テンプレートリテラル方式
const template = `
    <div class="container" id="newContent">
        Hello World
    </div>
`;
document.querySelector('#parent').insertAdjacentHTML('beforeend', template);

二つのアプローチの使い分け:

  • createElement: 要素を動的に操作する場合に適している
  • insertAdjacentHTML: 静的なHTML構造を追加する場合に便利
  • テンプレートリテラルを使うと、HTMLの構造が視覚的に分かりやすい

イベントの処理

JS
// jQuery
$button.on('click', function(e) {
    e.preventDefault();
    $(this).addClass('clicked');
});

// イベントの削除
$button.off('click', handleClick);

// Modern JavaScript
button.addEventListener('click', function(e) {
    e.preventDefault();
    this.classList.add('clicked');
});

// イベントの削除
button.removeEventListener('click', handleClick);

// イベント委譲
document.querySelector('.list').addEventListener('click', e => {
    if (e.target.matches('.item')) {
        handleItemClick(e);
    }
});

モダンなイベント処理のポイント:

  • addEventListenerは複数のイベントリスナーを登録可能
  • イベント委譲にはmatchesメソッドが便利
  • removeEventListenerを使う場合は、同じ関数参照が必要

実践的なリファクタリング例:タブUI

JS
// Before: jQuery
$('.tab').on('click', function(e) {
    e.preventDefault();
    var $tab = $(this);
    var target = $tab.data('target');
    
    $('.tab').removeClass('active');
    $tab.addClass('active');
    
    $('.tab-content').hide();
    $('#' + target).show();
});

// After: Modern JavaScript
document.querySelectorAll('.tab').forEach(tab => {
    tab.addEventListener('click', e => {
        e.preventDefault();
        const target = tab.dataset.target;
        
        // タブの切り替え
        document.querySelectorAll('.tab')
            .forEach(t => t.classList.remove('active'));
        tab.classList.add('active');
        
        // コンテンツの切り替え
        document.querySelectorAll('.tab-content')
            .forEach(content => content.style.display = 'none');
        document.getElementById(target).style.display = 'block';
    });
});

リファクタリングのポイント:

  • querySelectorAllforEachで要素の一括処理
  • datasetでdata属性にアクセス
  • クラスの切り替えはclassListを使用
  • 表示/非表示の切り替えは直接styleプロパティを操作

まとめ

DOM操作において、jQueryは確かに便利なAPIを提供してきました。しかし、モダンなJavaScriptを使うことで:

  • より軽量なコード
  • ネイティブAPIによる高速な処理
  • 明確な意図の表現
  • 余分な抽象化の排除

が実現できます。次回は「非同期処理の進化 – コールバック地獄からの解放」について解説し、Promise/async/awaitを使った効率的な非同期処理の書き方を学んでいきましょう。