JavaScriptのファイルって実は読み込む場所を間違えると思ったように動作しない経験ってありませんか?
自分は最初の頃よく遭遇していて、ググってもイマイチ理解できませんでした。
本来なら読み込みについては軽くスキップせずに理解すべきなんですが、学習教材やYoutubeではそこまで触れられていません。
「コピペして書いているのに自分のパソコンだと何故か動かないときがある」
「JSファイルの読み込みの種類の意味が良く分かっていない」
「deferっていうのを見かけるけど、何のためにあるかが説明されていないと思っている」
小さなミスで時間を取られないようにJSファイルの読み込みの基本をまとめてみました。
また動画もあるので一緒に確認してもらうと良いかもしれません。
HTMLの相対パスの基本
相対パスとは表示しているHTMLファイルから見たときに、挿入したいファイルがどこにあるのかを辿るものです。
以下のようなフォルダがあったとした場合、HTMLファイルとJSファイルが同じ並びにあります。
- index.html
- script.js
そのためscriptタグのsrc属性では「”./script.js”」と指定することによってscript.jsを読み込むことができるようになります。
「./」という記号が「同じ並びにある」という意味になっています。
一方で以下のようなフォルダになっている場合、HTMLファイルとJSファイルが同じ並びにありません。
- index.html
- js
|- script.js
HTMLと同じ並びにあるのはjsフォルダになっているので、一度jsフォルダを経由してscript.jsに辿り着くことになり、scriptタグのsrc属性には「”./js/script.js”」と書くことになります。
また別の例としてHTMLファイルの方がフォルダに格納されている以下のような場合もあります。
- html
|- index.html
- script.js
引き続きHTMLファイルとJSファイルが同じ並びにない上にscript.jsに辿り着くには一度上の階層まで上がる必要があります。
上の階層に上がるには「”../”」という記号を使って「”../script.js”」と書くことになります。
上の階層に上がったとしても以下のようにjsファイルもフォルダに格納されている場合もあります。
- html
|- index.html
- js
|- script.js
このような場合は「上の階層に上がる」「jsフォルダに入る」という2つの工程が必要になるため「”../js/script.js”」と書くことになります。
同じ並びにあるものは「./」で、上の階層にあるものは「../」で辿り着くということを覚えておきましょう。
scriptタグに”defer”と書いているのは何をしているのか?
「defer」について先に説明しています。
恐らくJSファイルの読み込みで「defer」から説明しているのは珍しいと思います。
以下のようなJSファイルを用意したケースを考えます。
const div = document.getElementById("div");
console.log(div);
続いて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 id="div">hello</div>
</body>
</html>
JavaScriptの内容としてはHTML要素を取得してコンソールに出力するだけのものです。
ここでdeferを削除してみたいと思います。
<!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" />
// deferを削除した↓
<script src="./script.js"></script>
</head>
<body>
<div id="div">hello</div>
</body>
</html>
なんとnullとなり正常に実行することができませんでした。
これがdeferの効果になります。
そもそもHTMLとJavaScriptは「上から下に向かって順番に実行していく」という性質を持っています。
①HTML側のheadタグでscript.jsを読み込む
②script.js側で「div」というidを持つ要素を取得しようとする
③①の時点では「div」というidを持つ要素はまだ登場していないのでnullになる
みたいな流れになっているんです。
ちなみにdeferがないパターンで以下のようにすると正常に動作します。
<!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" />
</head>
<body>
<div id="div">hello</div>
<script src="./script.js"></script>
</body>
</html>
HTML側でscriptタグの位置をbodyタグの直前に移動させただけです。
こちらだとscript.jsを読み込む時点で、すでに「div」というidを持つ要素を発見できているので正常に動作しているのです。
まず基本的な動作を理解しておくことが大事なんですね。
deferの説明に戻りますが、deferを書くことによって簡単に言うと「JSファイルの読み込みを遅らせる」ことができます。
そのためheadタグに書いていても動作するようになっているのが冒頭の内容ということです。
すべてbodyタグの直前に書く?
「じゃあ今後はbodyタグの直前に書くようにすれば安心だ」と思われた方がいるかもしれません。
半分は正解です。
試しに先ほどのコードに新しくmain.jsというファイルを追加してみたいと思います。
console.log("test");
const div = document.getElementById("div");
console.log(div);
2つのJSファイルが存在していることになりました。
この状況で以下のようにJSファイルをheadタグで「deferなし」で読み込んでみます。
<!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="./main.js"></script>
<script src="./script.js"></script>
</head>
<body>
<div id="div">hello</div>
</body>
</html>
script.jsに関しては先ほど同様にnullになってしまっていますが、main.jsについては正常に動作しています。
なぜこうなるのでしょうか?
JSファイルそれぞれのコードに要因があります。
main.jsについては「testという文字をコンソールに出力する」という内容です。
「testという文字をコンソールに出力する」というのはHTML側で何か要素を見つける必要はありませんよね。
そのためheadタグでdeferがなくとも動作したわけです。
bodyタグの直前に読み込むとdeferなくとも正常に動作させることができる一方、HTMLの最後で読み込むので速度に影響を及ぼすことがあります。
今回のような簡単なコードだと大差ないですが、複雑な内容になると話は変わってきます。
そういう意味でも必ずしもbodyタグの直前が正しいということにはならないのです。
つまり状況に応じて読み込む場所を考えましょう、ということですね。
初心者がやりがちな相対パスの書き方に対する間違った理解
最後に初心者の方がハマりがちな相対パスの書き方の違いを説明します。
例えば以下のようなファイル構成だった場合に、HTML側でJSファイルを読み込む相対パスはどうなるでしょうか?
- index.html
- script.js
src=”script.js”と書くことで読み込めるはずですよね。
なぜならHTMLとJSは同じ階層にあるからです。
続いて以下のようなファイル構成だった場合に、HTML側でJSファイルを読み込む相対パスはどうなるでしょうか?
- src
|- index.html
|- script.js
こちらはsrc=”./script.js”と書くことで読み込めますが、src=”/script.js”だと思った方がいるのではないでしょうか?
実は「.」の有無で全然違う意味になるのです。
src=”./script.js”とは「index.htmlと同じ階層にあるscript.js」という意味になり、今回のファイル構成に合っています。
一方でsrc=”/script.js”とは「ルートディレクトリにあるscript.js」という意味になり、今回のファイル構成には合いません。
ルートディレクトリとは1番上の階層だと思ってください。
今回はsrcフォルダのみが1番上の階層にあり、srcフォルダの中にindex.htmlとscript.jsがあるので「ルートディレクトリにあるscript.js」にはなりません。
ちなみに1個手前でやったルートディレクトリにファイルがある場合にはどちらも同じ意味になります。
- index.html
- script.js
script.jsはルートディレクトリになるのでsrc=”/script.js”と書いても読み込めるようになります。
もしくはsrc=”./script.js”と書いても、こちらも同じくファイル構成に合っていますので読み込めます。
「どちらも同じようなもの」というざっくり理解だとハマることになるので今のうちに違いを理解しておくようにしましょう。