Prompt
以下のようなjavascriptが、外部から
<script src="[パス]" defer></script>のように読み込まれる
function func(){/* */}そしてHTML内にこのボタンを配置する
<button type="button" onclick="func()">func</button>でも、動かない!一体なぜ?
えーっと、その状況だと、だいたい原因は**defer属性とonclickイベントハンドラ**の組み合わせにあると思うな! 🤔
結論から言うと、defer属性がついていると、HTMLが読み込まれてDOMツリーが構築された後にJavaScriptが実行されるんだ。でも、onclickイベントハンドラは、HTMLが解析されるタイミングで、つまりまだJavaScriptが読み込まれる前に**funcという関数を探してしまう**から、「そんな関数ないよ!」ってエラーになっちゃうんだよね。
じゃあ、なんでかっていうと、ブラウザの処理の流れを分解して考えてみよう。
1. ブラウザの処理の流れ
-
HTMLの解析: ブラウザがHTMLファイルを上から順に読み込んで、DOMツリーを構築していくよ。このとき、
<button onclick="func()">の行に差し掛かると、「お、onclickイベントハンドラがあるな。funcっていう関数を探そう」ってなるんだけど、まだJavaScriptは実行されてないから、funcが見つからなくてエラーになるんだ。 -
JavaScriptのダウンロード:
<script src="..." defer>の行を見つけると、ブラウザはバックグラウンドでJavaScriptファイルのダウンロードを開始するよ。deferがついているから、ダウンロードはするけど、実行は後回しにするんだ。 -
DOMツリーの構築完了: HTMLファイルの最後まで読み込み、DOMツリーが完全に構築される。
-
JavaScriptの実行: 最後に、ダウンロードが終わったJavaScriptファイルが実行される。ここでようやく
func関数が定義されるんだ。
このタイムラグが問題なんだよね。onclickが関数を呼ぼうとしたときには、まだ関数が定義されてないっていう状況になっちゃってる。
2. 解決策
この問題を解決する方法はいくつかあるよ!どれもシンプルで、状況に応じて使い分けられるから、知っておくと便利だよー。
① deferを外す
これが一番簡単!defer属性を外せば、JavaScriptはHTMLの解析中に同期的に読み込まれて実行されるようになる。ボタンのHTML要素が解析される前にfunc関数が定義されるから、エラーにならなくなるよ。
HTML
<script src="[パス]"></script>
<button type="button" onclick="func()">func</button>
② deferを残したまま、イベントリスナーを使う
これは個人的におすすめの方法。HTMLとJavaScriptをきれいに分離できるから、メンテナンス性が高くなるんだ。defer属性を残したまま、JavaScript内で**DOMContentLoadedイベント**を使って、DOMツウザリーが完全に構築された後にイベントリスナーを設定するんだ。
HTML
<button type="button" id="myButton">func</button>
<script src="[パス]" defer></script>
JavaScript
// 外部のJavaScriptファイル内
document.addEventListener('DOMContentLoaded', () => {
const button = document.getElementById('myButton');
if (button) {
button.addEventListener('click', func);
}
});
function func() {
// 処理
console.log('ボタンがクリックされたよ!');
}
この方法だと、HTMLが完全に準備できてからイベントリスナーを登録するから、func関数が定義されていないってことは起こらないよ!
どう?面白いでしょ?プログラミングって、こういうブラウザの動作の裏側を知ってると、もっと楽しくなるよね!😉