【脱jQuery】jQuery → JavaScript (VanillaJS) 書き換え(1)基礎知識のまとめ/基本ルール(備忘録)
まえがき
-
到達目標
今までなあなあでjQuery依存のJSを書いてきたコーダーの自分が、初歩的なJSの基礎を理解し、簡単な処理であればVanillaJSで実装できるスキルを身につけること
心得
- IE対応は考えないこと前提とする。
- Web制作においては HTML / CSS の範囲内で解決することに極力努める。JSはあくまで動作の切り替えを実施するための、クラス名の単純な付け外し程度に留めることを目標に(可能な範囲で)
- 引数などを活用し、フレキシブル(テンプレートのようにいかなる場合でも対応可能)であることに拘った複雑な関数を無理に自作する必要はない。
【理由】完璧主義に偏り過ぎると、それが仇となり逆に使い勝手が悪く破綻する可能性が高い。
【例】ページ内に同様の(複雑な)挙動が2箇所(複数箇所)必要な場合 → HTMLソースコードには2箇所に別々のクラス名を付け、同様の関数を2つ定義し hoge01(); hoge02(); 宣言で対応可能。 - 「三項演算子」「for (... of ...) 」「アロー関数」を始めとする、シンプルで簡略化した書き方。
→ まだ慣れないうちはむやみに使わないように。また、仕様がわかるように極力コメントアウトでメモを残す(多すぎても読みづらくなるので、そこは適宜コード外に記すなり、臨機応変に) - if文の入れ子(ネスト)構造など、アンチパターンは極力避けたいが、各条件の分岐時に処理が必要など、意図があるのであれば許容する(ただし階層が深くはならないように配慮すること)
補足・メモ
そもそも「DOM」とは?
おそらく厳密には違う(かもしれない)ものの、Web制作におけるJSの全体像を(おおまかにでも)理解するにおいては、ひとまず以下の認識で問題ないように思われる。
(わかるに越したことはないが、最初のうちはあまりピンとこなくても、なんとなく「そういうもの」だという認識で理解しておけば。やっていくうちにわかることも多々あるので)
DOM
- 各WebページにおけるHTMLソースコード全体を、ツリー状の階層構造を持つモデルとして見る考え方。
Node
- DOMのツリーを構成する、一つひとつの要素(『オブジェクト』と呼ばれる)
- HTMLソースコードの中で言えば、bodyタグ以下(headerや各section、コンテンツのdivタグ、footer等)の各タグのことを指す。
- 親子の階層を持つという基本理念から、あるタグから見た場合の親要素や子要素にあたるタグ
→ そのノードから見た「親ノード」や「子ノード」という位置に該当。
Element
- Nodeの種類の1つ。Element型のNodeのこと。
- <p>テキスト</p> のように「タグ」+「その内容」の1つの塊のこと。
イベント実行タイミングの指定・よくある書き方
世間ではよく「おまじない・呪文」と言われている、処理を実行するタイミングを指定して処理全体を囲む関数(テンプレート的な記述)の備忘録
補足・メモ・注意事項
- 「scroll」や「resize」はその動作が行われるたびに関数が呼び出される。
→ パフォーマンスに悪影響することもあり、以後可能な場合は Intersection Observer を使うのが好ましいかもしれない。
(あくまでこの記事は『jQuery → Vanilla JS への書き換え、およびその情報整理と備忘録』を目的としているため、「Intersection Observer」に関する解説は、ここでは割愛する)
// (1) DOMツリーが構築された = HTMLが読み込まれた時点(画像などはまだ読み込まれていないタイミング)で実行
$(function(){
// ここに処理を書く(以下省略)
});
// 次の書き方でも代替可能(というよりも、jQuery 3.0以降では簡略化された上記の書き方が主流だが、本来は以下の書き方が正しい。$→jQueryでも同じ意味になる)
$(document).ready(function(){
});
// 【WordPressへの対応などで頻出】先頭の$→jQuery, function()→function($)に置換する必要あり(カプセル化)
// ※ また、あくまで個人的な肌感覚(経験)としては「jQueryのバージョンがかなり古い」「処理のタイミングに差がある」といった場合でも、上記の「おまじない」をこの書き方に置換したことで動かない→動く可能性が少なからず考えられる(気がする)
jQuery(function($){
// 先頭の $ 宣言により「jQuery以外の他のJSライブラリの(グローバル〇〇といった)内容」が読み込まれてコンフリクト発生 → 動かない
// このように「最初(先頭の宣言)に$が使えない環境」では、最初に「jQuery」の指定が必須
// function()→function($)への置換は、中身の処理の記述で一般的な $() の書き方(関数)を使う上で必要。→function(hoge)とした場合は hoge() で対応できるが、誰も得しないため、とりあえず上記の「おまじない」を覚えておけばよい。
});
// (2) ロード完了後 → DOMツリー構築 + 画像などページ内のコンテンツ全体の読み込みが完了した時点で実行
$(window).on('load', function(){
});
// (3) ページ内でスクロール動作が行われたときに実行
$(window).on('scroll', function(){
});
// (4) ブラウザの表示域のリサイズが行われたときに実行
$(window).on('resize', function(){
});
// 補足:(2)〜(4)の記述は次のようにまとめて書くことも可能
// (なお、VanillaJS における addEventListener では、次のように複数の状況におけるイベントをまとめて記述することは不可能)
$(window).on('load resize', function(){
// ページ内のすべての要素の読み込みが完了後に実行される + ブラウザの表示域のリサイズが行われた際に実行される処理
// 使用例:そのときのブラウザの横幅(表示幅)を取得し、一定のサイズ以上(以下)でPC画面(パソコン)/タブレット画面/SP画面(モバイル)用の処理(切り替え等)を動的に行う際に活用することができる
});
// (1) DOMツリーが構築された = HTMLが読み込まれた時点(画像などは読み込まれていないタイミング)で実行
document.addEventListener('DOMContentLoaded', function(){
// ここに処理を書く(以下省略)
});
// (2) ロード完了後 → DOMツリー構築 + 画像などページ内のコンテンツ全体の読み込みが完了した時点で実行
window.addEventListener('load', function(){
});
// (3) ページ内でスクロール動作が行われたときに実行
window.addEventListener('scroll', function(){
});
// (4) ブラウザの表示域のリサイズが行われたときに実行
window.addEventListener('resize', function(){
});
// 補足:即時関数 → DOMツリーの構築なども待たず、即座に処理を実行したい場合
(function(){
}());
要素の取得メソッド
下記のHTMLソースコードを一例に解説
<ul id="list-block">
<li class="list">リスト</li>
<li class="list">リスト</li>
<li class="list">リスト</li>
</ul>
CSSセレクタ形式でタグの取得方法(単一)
document.querySelector('.list');
【特徴】
- CSSセレクタ形式で指定可能なので、入れ子での指定やタグそのものの指定も使える
- jQueryの書き方と似た感じで使い勝手がよい。
【返り値】
- Elementオブジェクト(単一:ページ内に複数ある場合は、上から順に見て最初の要素)
- ページ内に無い → null
CSSセレクタ形式でタグの取得方法(複数、該当するのものすべて)
document.querySelectorAll('.list');
【特徴】
- 返り値がNodeListである以外は、上記の.querySelectorメソッドと同じ
【返り値】
- NodeList
- ページ内に無い → 0のNodeList
★★ 取得メソッドは基本的に上の2つの使用を中心とする★★
パフォーマンス面(スピード)では getElementById や getElementsByClassName に劣るが、使い勝手と汎用性の高さを考慮して。
以下に、念のために他の取得メソッドも一部まとめておくが、あまり出番はないかもしれない。
id(id属性の付いたタグ、単一)の取得方法
document.getElementsById('list-block');
【返り値】
- Elementオブジェクト(単一)
- ページ内に無い → null
class(class属性の付いたタグ)の取得方法
document.getElementsByClassName('list');
【返り値】
- HTMLCollection
- ページ内に無い → 空のHTMLCollection
HTMLタグの指定 → 取得方法
document.getElementsByTagName('li');
【返り値】
- HTMLCollection
- ページ内に無い → 空のHTMLCollection
複数の要素取得 → 配列変換
document.querySelectorAll(); 等によって複数の要素を取得した場合
→ デフォルトの返り値は NodeList や HTMLCollection
→ 配列と似ているが厳密には違う(そのままだと使い勝手が悪い/不都合が生じる場合も考えられる)
→ 配列(Array)へ変換の必要あり
(個人的にはスプレッド構文の記述が簡単でわかりやすいので、そちらの使用を基本ルールとしたいところ)
// 例:複数の <div class="item"> 要素を取得する場合
// このままの使用は【NG】
const itemList = document.querySelectorAll('.item');
Array.from方式で配列変換する場合
let itemList = document.querySelectorAll('.item'); // 変数宣言:var でも代用可能
itemList = Array.from(itemList);
// 別解:const
const items = document.querySelectorAll('.item');
const itemList = Array.from(items); // 定数:const は再代入不可能 → 新たな宣言が必要
// 別解:一気に宣言
const itemList = Array.from(document.querySelectorAll('.item'));
スプレッド構文を利用する場合
const itemList = [...document.querySelectorAll('.item')];
注意点
スプレッド構文
取得できる引数には上限がある。しかし、普通にコーディングでJSを使う過程で、1つの処理においてそこまで大量のデータを取り扱う機会もそうそう無いと思うので、この点はとりあえず頭の片隅に留意しておく程度の意識で。
複数の要素への対処
下記のHTMLソースコードを一例に解説
<ul>
<li>
<div class="item">クリックすると親のliタグにクラス名のつけ外しイベント発生</div>
</li>
<li>
<div class="item">クリックすると親のliタグにクラス名のつけ外しイベント発生</div>
</li>
<!-- 以下、反復 -->
</ul>
【jQuery】の場合
同一ページ内に存在する、対象となる複数のNode(同じクラス名が付けられたHTMLタグなど)に対して、jQuery依存のJS操作では
- すべて同時に、まとめて同じ操作をする場合
→ 単一のNodeへの処理を書く場合と同様にコードを書くことで実装可能。 - 各要素に対してそれぞれ独立した処理を実行(独立した挙動を実装)したい場合
→ each()メソッド や $(this) を用いて直感的にコードを書くことで対応可能。
以上の通り、比較的簡単に処理・動作を実装することができた。
// jQuery依存で実装する場合
$('.item').each(function() {
// ここに処理を書く
$(this).on('click', function(){
$(this).parent().toggleClass('is-clicked');
});
});
// いっそ、このような単純なクリックのイベント等であれば、次のようにeach()メソッドすら無くても特に問題はない
$('.item').on('click', function(){
$(this).parent().toggleClass('is-clicked');
});
【VanillaJS】の場合
複数のNodeに対して、VanillaJS では
(1)まず該当する要素をNodeList/HTMLCollection/配列等でオブジェクトとして取得(配列への変換については前項のシートを参照)
(2)for文、forEachメソッド等を用いてオブジェクトにループ処理を実行する
以上の対応をするのが基本。
// 上記の処理をjQuery無しで実装する場合
const items = [...document.querySelectorAll('.item')];
for ( let i = 0; i < items.length; i++ ) {
items[i].addEventListener('click', function() {
let parent = items[i].parentElement;
parent.classList.toggle('is-clicked');
});
}