「JavaScriptを使っているけど実際に何がどうなっているのかまでは理解できてる自信がない」
「Web制作くらいはできるがwindowとかdocumentとか言われるとわからない」
「”〜〜map is not a function〜〜”というエラーを出したことがある」
本日はそんな方に向けて書いていきます。
JavaScriptを学ぶ過程で、多くの初学者が直面する共通の課題があります。
それは、DOM(Document Object Model)という複雑な概念の理解です。
DOMは、ウェブページの構造やコンテンツを表現し、JavaScriptを使用してそれらの要素にアクセスし操作するための基本的な方法です。
しかしDOMは初めて学ぶときには混乱することがあり、その理解を深めるための教材や記事は少ないです。
今回はJavaScriptでよく登場するキーワードを解説していきながら、ぼんやり理解のDOMをイメージしてもらうようになっています。
初心者の方はもちろん、すでにWeb制作などができている方にも知って損はない内容なので最後まで読んでいってください。
また動画もあるので必要に応じてご覧ください。
JavaScriptで登場するwindowとdocumentは何者なのか?
JavaScriptもしくはjQueryでアニメーションやイベント処理を作る方法はググったり学習教材で知ることができるのですが以下のようなコードを見たことがあるかと思います。
const el = document.querySelector("div");
window.addEventListener("load", () => {
// 処理の内容
});
document.addEventListener("DOMContentLoaded", () => {
// 処理の内容
});
HTMLの取得や処理の実行にwindow,documentという言葉が頻繁に登場します。
スクールや学習教材でwindowとdocumentが何かを教えてもらったことのある方は少ないのではないでしょうか。
こちらコンソールに出してみましょう。
console.log(window);
console.log(document);
そうするとwindowはよくわからない言葉が並んでいますが、ざっくりオブジェクトで出来ていることがわかります。
documentについては中身がHTMLの構成になっていますね。
簡単にいうとwindowはブラウザ画面のことで、documentはブラウザに表示しているHTMLになります。
そのためdocumentはwindowという大きなオブジェクトの中身の一部とも言えます。
試しにwindow.documentとしたコンソールも見てみましょう。
console.log("window.documentの結果:", window.document);
console.log("documentの結果:", document);
そうすると同じものが表示されます。
中身を開けると両方ともHTMLが入っていますね。
「windowもdocumentも同じようなもの」という理解でいた方もいると思います。
それぞれ別のものですがwindowの一部がdocumentである以上は、そういった理解でも良いかと思います。
ちなみにですがiframeタグを使ったYoutube動画の埋め込みでも同じようなことを体験できます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./style.css" />
<script src="./script.js" defer></script>
</head>
<body>
<h1>タイトル</h1>
<p>テキストです。</p>
<iframe
class="iframe"
width="560"
height="315"
src="https://www.youtube.com/embed/F6TxuADYRKg?si=jmCEg7pCN1ZOlIst"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
</body>
</html>
Youtubeは動画の埋め込みが出来てiframeタグというものを使っています。
検証ツールでそのiframeタグを見てみると中にdocumentがあり、documentの中身がHTMLになっていることがわかります。
つまりHTMLの中にHTMLがあるということですね。
iframeタグ自体はYoutube埋め込みのように、「ページのなかに別のページを埋め込む」という役割を持っています。
JavaScriptで登場するaddEventListenerは何者なのか?
windowとdocumentがざっくりイメージできたところで、そのwindowとdocumentに対してよく使われるaddEventListenerについても知っておきましょう。
「アニメーションを作る時に使うメソッド」という理解が出来ていればOKです。
JavaScriptにおけるaddEventListenerの使い方
addEventListenerは第1引数にイベント名、第2引数に処理内容を書きます。
例えばこんな感じです。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./style.css" />
<script src="./script.js" defer></script>
</head>
<body>
<div class="box"></div>
</body>
</html>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
margin: 16px;
}
.box {
width: 300px;
height: 300px;
background: #eee;
}
.text {
background: #666;
}
const box = document.querySelector(".box");
box.addEventListener("click", () => {
console.log("box");
});
class=”box”としたdivタグをクリックするとコンソールに「box」という文字を表示する処理です。
addEventListenerはdocument自体、documentのなかにある特定のHTMLタグに対して使用できます。
複数のHTML要素にaddEventListerを使う方法
よくあるのが同じアニメーションを複数のHTML要素に共通処理として実行させるケースです。
例えばHTMLで以下のようになっているとします。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./style.css" />
<script src="./script.js" defer></script>
</head>
<body>
<div class="box"></div>
<ul>
<li class="list">aaa</li>
<li class="list">bbb</li>
<li class="list">ccc</li>
</ul>
</body>
</html>
const lists = document.querySelectorAll(".list");
lists.forEach((list) => {
list.addEventListener("click", () => {
console.log("list");
});
});
class=”list”という要素が3つあり、全てに同じ処理をしたい場合は繰り返しのなかにaddEventListenerを書けばOKです。
class=”list”の要素が3つありますが、どれをクリックしてもコンソールに表示が出来ていますね。
注意としては要素を取得するときはquerySelectorではなくquerySelectorAllを使います。
querySelectorだと該当するclass名が複数あったとしても最初の1つしか取得できないからです。
NodeListと配列の違い
ちなみに今のようにquerySelectorAllで複数の要素を取得した場合、コンソールで確認したように配列のような形で取得できます。
初心者の方はquerySelectorAllで取得したものを配列だと思われるかもしれませんが、こちらは配列ではなく「NodeList」というものになります。
配列とNodeListはコンソールで確認すると同じものに見えますので注意しましょう。
const lists = document.querySelectorAll(".list");
const arr = ["aaa", "bbb", "ccc"];
console.log("NodeList:", lists);
console.log("配列:", arr);
NodeListと配列が違うものであることはメソッドを使うとわかります。
例えば繰り返し処理で使うmapメソッドです。
こちらをそれぞれ使ってみるとNodeListではエラーになり使えないことが確認できます。
const lists = document.querySelectorAll(".list");
const arr = ["aaa", "bbb", "ccc"];
arr.map((a) => {
console.log(a);
});
lists.map((list) => {
console.log(list);
});
mapメソッドは配列のために用意されたメソッドであり、NodeListで使うとエラーになります。
ちなみに使えるメソッドはプロトタイプとも呼ばれてコンソールで確認できます。
const lists = document.querySelectorAll(".list");
const arr = ["aaa", "bbb", "ccc"];
console.log("NodeList:", lists);
console.log("配列:", arr);
配列の方は数えきれないくらいのメソッドが表示され、これら全てのメソッドを使うことができます。
一方でNodeListの方は配列と同じメソッドもありますが圧倒的に数が少ないですね。
またmapメソッドが存在していません。
ちなみにNodeListで繰り返しをしたいときはforEachが使えます。
先ほどの繰り返し処理でforEachを使っていたのはNodeListにはforEachを使うしかないからです。
querySelectorAllで取得したものは配列ではありません、必ず理解しておきましょう。
「正しく書いているはずなのにエラーになる」というときは、もしかしたら使えないメソッドを書いていることが原因かもしれません。
JavaScriptのaddEventListerで使うe(イベント)とは?
addEventListenerを使うことでアニメーションなどの処理を実行することができました。
アニメーションの作り方を調べているときにaddEventListenerの引数にeを入れているものを見たことがあるかもしれません。
eはイベントという意味になります。
addEventListenerの使い方としては引数は任意なのですが、eを書いたときに何が起きているのかまでは習っていない方が多いはずです。
eはイベント情報がオブジェクト形式になっている
例えば以下のようなものを書いてみます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./style.css" />
<script src="./script.js" defer></script>
</head>
<body>
<button class="btn">クリック</button>
</body>
</html>
const btn = document.querySelector(".btn");
btn.addEventListener("click", (e) => {
console.log(e);
});
ボタンをクリックしたらeをコンソールに表示してみます。
わからないものはコンソールに表示して確認してみるのはプログラミング学習でとても大事です。
以下がボタンをクリックしたときのコンソールの内容です。
windowのときのように大量のデータがオブジェクトとして形成されています。
eはイベント、つまりクリックしたときに関連する情報のすべてが詰まっています。
またeはオブジェクトになっているので特定の情報はプロパティ名を指定すれば値が取得できます。
例えばクリックしたときのaddEventListenerを以下のようにしてみます。
const btn = document.querySelector(".btn");
btn.addEventListener("click", (e) => {
console.log(e.clientX);
});
e.clientXとするとクリックしたときのX座標を取得することができます。
ボタンのなかでもクリックする位置によっていろんな数字がコンソールに表示されるのがわかります。
ちなみにclientXというプロパティ名はどこから来たかというと先ほどのeをコンソールに出した時の中身の一つです。
すでにプロパティ名と役割は決まっていて覚える必要はありませんが、「eを使うといろんな情報が取れるんだな」と思っておいてもらえれば大丈夫です。
またeのプロパティの種類はイベントによっても違います。
例えばHTMLでフォームを追加してみます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./style.css" />
<script src="./script.js" defer></script>
</head>
<body>
<button class="btn">クリック</button>
<form action="">
<input type="text" class="input" />
<button class="submit">送信</button>
</form>
</body>
</html>
JavaScriptではinputタグを取得してaddEventListenerを作ってみましょう。
入力欄に文字を入力したらコンソールにeを表示します。
const btn = document.querySelector(".btn");
const input = document.querySelector(".input");
btn.addEventListener("click", (e) => {
console.log(e.clientX);
});
input.addEventListener("input", (e) => {
console.log(e);
});
コンソールにオブジェクトが表示されるのは同じですが、よく見ると「PointerEvent」「InputEvent」のように名前が違うのと中にあるプロパティも微妙に違います。
このようにイベントの種類によって取得できるe、つまり情報が変わってくることも覚えておいてください。
例えばですがInputEventには先ほどのe.clientXは使えません。
const btn = document.querySelector(".btn");
const input = document.querySelector(".input");
btn.addEventListener("click", (e) => {
console.log(e.clientX);
});
input.addEventListener("input", (e) => {
console.log(e.clientX);
});
clientXはクリックした時の座標ですので、入力欄に文字を入れることには座標は関係ないからです。
そのためInputEvent、つまり文字の入力にe.clientXとしてもundefinedが表示されます。
「今は何のイベントを作っているのか?」というのは意識できると良いですね。
e.target.valueとは?
Reactなど多くのJavaScriptの開発で「e.target.value」というものが登場します。
先ほどやったinputタグの入力された値を取得するもので、登場頻度が多いので暗記してしまっている方も多いと思います。
せっかくなのでコンソールでeのオブジェクトを表示させてtargetプロパティを探してみましょう。
eのプロパティは大量にありますが英語順に並んでいるので見つけられるはずです。
targetプロパティもオブジェクト形式になっていて中身があります。
targetプロパティの中にあるプロパティも多いのですがvalueというプロパティに入力された文字があります。
これを取得していたわけですね。
eのtargetプロパティのvalueプロパティの値は「e.target.value」という書き方になるので、inputタグの入力された内容を取得するときは「const val = e.target.value;」のようにすればできるわけです。
ちなみにtargetプロパティは「今いるHTML要素」という意味で、inputタグであれば「targetはinput」という感じになります。
何となくコピペしていたコードにも意味があるんですね。
e.preventDefault()とは?
同じようによく登場するものに「e.preventDefault();」というものがあります。
こちらは書き方からイメージされるようにメソッドになっています。
eに標準で準備されているメソッドにpreventDefault()というものがあるわけです。
こちらはデフォルトの挙動をリセットするときに使います。
例えばformタグを設置するとキーボードのEnterキーを叩くことで送信と画面遷移が実行されるようになっています。
これは私たちがコードで書くというより最初からある仕様です。
Enterキーを叩いても画面遷移させずに同じ画面に止まりたいケースにはeにpreventDefault()を実行しないといけないわけです。
そのため必ず書くものというより要件に合えば書くといった感じです。
「とりあえず毎回書いている」という初心者の方がいるかもしれませんね。
イベントで注意しないといけないこと
addEventListenerを使ったイベント処理では初心者の方がハマりやすいケースがあります。
例えばHTMLとCSSが以下のようになっていたとします。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./style.css" />
<script src="./script.js" defer></script>
</head>
<body>
<div class="box">
<p class="text">テキストです</p>
</div>
</body>
</html>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
margin: 16px;
}
.box {
width: 300px;
height: 300px;
background: #eee;
}
.text {
background: #666;
}
上記の状態にJavaScriptでイベント処理を作っていきます。
const box = document.querySelector(".box");
const text = document.querySelector(".text");
box.addEventListener("click", () => {
console.log("box");
});
text.addEventListener("click", () => {
console.log("text");
}
);
class=”text”の要素をクリックするとclass=”box”の方で設定していたイベント処理と一緒に実行されましたね。
これはHTMLのマークアップで入れ子になっているからです。
入れ子で要素が配置されているとき、親と子で同じクリックイベントを設定していると子の方をクリックしたときに自動的に親のクリックイベントも実行されるようになっているからです。
これを止めたい場合はeにあるメソッドを実行しておくと良いです。
const box = document.querySelector(".box");
const text = document.querySelector(".text");
box.addEventListener("click", () => {
console.log("box");
});
text.addEventListener("click", (e) => {
e.stopPropagation();
console.log("text");
}
);
e.stopPropagation();とすると入れ子の二重実行をキャンセルできます。
addEventListenerは基本的に第1引数にイベントの種類、第2引数に処理内容を書くのは前半で説明しました。
それに加えて第3引数で任意のオプションを設定することができます。
例えば第3引数に{once : true}とすればイベント処理を1回だけにすることができます。
const box = document.querySelector(".box");
const text = document.querySelector(".text");
box.addEventListener("click", () => {
console.log("box");
});
text.addEventListener("click", (e) => {
e.stopPropagation();
console.log("text");
}, { once: true }
);
クリックイベントだと何回でもクリックできますよね。
それを1回クリックしたら2回目以降はクリックしても第2引数に書いた処理は実行しないようにできます。
こちらの設定をしていると先ほどのe.stopPropagation();を書いていたとしても、2回目以降の子のクリックでは親のクリックが実行されます。
親子になっているそれぞれにクリックイベントを設定していて、子の第3引数に{once : true}があることで子のクリックイベントは2回目以降は実行されません。
一方で親のクリックイベントはクリックした回数だけ処理が実行されるので、子の代わりに実行されるようになっています。
ここまで来るととても限定的なケースですが1回は試しに練習してみることをお勧めします。
イベントの種類、eのプロパティとメソッドは覚えきれないくらいあります。
それらを全て暗記することは不可能で、練習や実務で経験していくしかありません。
最初はできなくて当然なので1個ずつ理解していきましょうね。