Open20

イベント

mabomabo

イベントリスナー

常に背景で待ち受けている処理や関数のこと。
あるイベントが発生した時に実行される・呼び出される関数のこと。
つまり、イベントが発生するまで何もしないで待ち続け、イベントが発生した瞬間に処理を実行する。
これにより、ソフトウェアが認識できるアクションに応じて、データを処理することができる。


https://recursionist.io/dashboard/course/16/lesson/305 より画像引用

イベントリスナーは、まさにコールバック関数と連想配列(またはオブジェクト)のような仕組みを使って管理されます。

イベントリスナーの仕組み

イベントリスナーは「コールバック関数」と「それを管理する仕組み」の組み合わせで実現されています。

1. コールバック関数の用意

イベントリスナーは、特定のイベントが発生したときに「呼び出される」関数なので、その関数自体をあらかじめ用意しておく必要があります。この呼び出される関数をコールバック関数と呼びます。

// イベントが発生したときに実行されるコールバック関数
function handleClick() {
  console.log("ボタンがクリックされました!");
}

2. 関連付けと管理

次に、どのイベント(例:'click')がどの要素(例:ボタン)で発生したときに、どのコールバック関数(例:handleClick)を実行するのか、という情報をブラウザの内部で管理する必要があります。この管理は、連想配列やオブジェクトに似たデータ構造で行われます。

概念的には、ブラウザは以下のようなリストを持っています。

// ブラウザが内部的に管理するデータ構造のイメージ
{
  'click': [
    { target: button, callback: handleClick },
    { target: link, callback: handleLinkClick }
  ],
  'mouseover': [
    { target: image, callback: showDescription }
  ]
}

この仕組みがあることで、ブラウザはクリックイベントが発生した際に、どの要素で発生したかを確認し、その要素に紐づけられたコールバック関数を正確に呼び出すことができるのです。

イベントリスナーの設定

JavaScriptからのWeb APIへのアクセス
JavaScriptコードからaddEventListener()メソッドを呼び出すことで、Web APIにイベントリスナーの登録を要求する。
これは、JavaScriptがブラウザの機能(Web API)にアクセスしていることを意味する。

イベントリスナーの登録
Web APIは、指定された要素、イベントの種類、イベントハンドラ(コールバック関数)を関連付けて登録する。
これにより、ブラウザは特定のイベントが発生したときに、どの関数を実行すべきかを記憶する。

イベントリスナーの役割
イベントリスナーは、特定のイベントが発生したときに、どの関数(イベントハンドラ)を実行するかをブラウザに伝える役割を担う。
イベントリスナーが登録されていなければ、ブラウザはどのイベントハンドラを実行すべきかを知ることができません。
→ イベントハンドラはないため、実行されない。

イベントの監視
ブラウザは、登録されたイベントリスナーに基づいて、指定された要素で発生するイベントを監視する。

イベントの流れ

https://java2005.cis.k.hosei.ac.jp/materials/lecture24/swing2.html より画像引用

イベントリスナー登録の目的

イベントハンドラの関連付け
addEventListener()メソッドを使用してイベントリスナーを登録することで、特定のDOM要素と特定のイベントが発生したときに実行するイベントハンドラ(コールバック関数)を関連付ける。

イベント処理のカスタマイズ
これにより、開発者は特定のイベントが発生したときに、自分のWebページに必要な独自の処理を記述できる。

イベント処理の分離
イベントリスナーを使用することで、イベント処理のコードをHTML要素から分離できる。これにより、コードの可読性や保守性を向上させることができる。

イベント発生時の処理

イベントが発生すると、ブラウザは登録されたイベントリスナーを検出し、関連付けられたイベントハンドラを呼び出します。
イベントハンドラは、イベントオブジェクトを引数として受け取り、イベントに関する情報を使用して必要な処理を実行します。

イベントリスナーは、イベントが発生したときに実行される関数(イベントハンドラ)を登録する役割を担う。
イベントリスナー自体がイベントを検出するわけではない。イベントを検出するのはブラウザ
イベントリスナーは、ブラウザが検出したイベントに対して、どのような処理を行なうかを定義するために使用される

mabomabo

イベント(event)

Web ページ上で、ボタンをクリックしたり、キーボードを押したり、画像やテキストをマウスでカーソルを合わせたりすると、コンピュータソフトウェアがそれらの動作を検知することができる動作のこと。


https://quix.io/blog/what-why-how-of-event-driven-programming より画像引用

イベントには、①標準イベント と ②カスタムイベント がある。
①はブラウザやDOMによって事前に登録されている
②は開発者が独自にイベントを定義できる。
→ 'click'のような文字列を自分でカスタマイズすることができる。
・new Event("開発者が定義する文字列") は、"[開発者が定義した文字列]" という名前の新しいカスタムイベントオブジェクトを作成する。
・このイベントは、標準のイベントとは異なり、特定のHTML要素やブラウザの動作によって自動的に発生することはないため、開発者は、dispatchEvent() メソッドを使用して、このカスタムイベントを明示的に発生させる必要がある。

イベントリスナー(event listener)

イベントが発生したときに呼び出される関数のこと。
イベントリスナーは、イベントを監視して、イベントが発生した場合に適切な処理を行うために使用される。
例えば、ウェブページでボタンがクリックされた場合、イベントリスナーがボタン要素に登録され、ボタンがクリックされたときに呼び出される。
イベントリスナーは、イベントが発生するまで待機し、イベントが発生したときに初めてイベントハンドラを呼び出す役割を果たす。

・イベントリスナーは、要素のインスタンスに紐づいて管理される。これにより、各要素は個別にイベント処理を持つことができる。
・イベントリスナーは、イベントが発生したときに、その要素のインスタンスに登録されたイベントハンドラーを実行する。

ブラウザは、ウェブページ全体のイベント処理を管理する。つまり、どの要素でどのイベントが発生したかを検出し、適切なイベントハンドラーを実行する役割を担う。
ブラウザは、イベント伝播(イベントがDOMツリーをどのように伝わっていくか)の仕組みも管理する。

イベントハンドラ(event handler)

① Web APIによって追加された非同期処理の結果であるコールバック関数
setTimeoutやfetchなどの非同期処理が完了した際に実行されるコールバック関数。

② ユーザーの操作やブラウザのイベントに対応するコールバック関数
clickイベントやloadイベントなど、ユーザーの操作やブラウザのイベントに対応するために登録されたコールバック関数。

イベントハンドラは、特定のイベント(クリック、キー入力など)が発生したときに実行される関数のこと。オブジェクトのイベントが発生したときに実行される、ステートメントを含む関数。
イベントハンドラは、イベントリスナーを通じてブラウザに登録され、イベントが発生するとブラウザによって呼び出される。
※addEventListener メソッドの第二引数に指定される関数はコールバック関数であり、イベントハンドラである。

イベントハンドラは、イベント処理の実際のロジックを記述するために使用される。
例えば、ボタンがクリックされた場合に、そのボタンが何をするかを決定するコードブロックがイベントハンドラとして定義される。

イベントハンドラは、通常、イベントリスナーと組み合わせて使用される。
イベントリスナーがイベントを検知すると、それに応じたイベントハンドラが呼び出され、処理が実行される。イベントハンドラは、JavaScript などのプログラミング言語でよく使われ、Web ページのユーザーインタラクション、ページの読み込み完了、タイマーイベントなど、様々なイベントに応じた処理を実行するために使用される。

コールバック関数とは、イベントリスナーに登録された関数のことで、イベントが発生したときに実行される。コールバック関数には、イベントオブジェクトと呼ばれる引数が渡される。イベントオブジェクトには、どのようなイベントが発生したのか、いつ発生したのか、なぜ発生したのかなど、発生したイベントに関する情報が含まれている。

JavaScript において、関数は通常何かしらの値を返す。そのため、コールバック関数も値を返すように実装されていることがある。しかし、イベントリスナーに登録されるコールバック関数は、イベントが発生した時に実行される処理を実行するだけで、通常は何も値を返さないように実装される。

ブラウザは、イベントループと呼ばれる仕組みを使って、イベントの発生とイベントハンドラの実行を効率的に処理する。

イベントハンドラの実行

イベントの発生
監視対象の要素でイベントが発生すると、ブラウザはイベントオブジェクトを生成する。

イベントキューへの追加
イベントオブジェクトと、それに対応するイベントハンドラがイベントキューに追加される。

イベントループによる処理
イベントループは、コールスタックが空になった時点で、イベントキューからイベントハンドラを取り出し、コールスタックにプッシュする。

イベントハンドラの実行
コールスタックにプッシュされたイベントハンドラが実行される。
このとき、イベントオブジェクトがイベントハンドラの引数として渡される。

重要なポイント

・addEventListener()メソッドは、イベントリスナーを登録するだけであり、イベントハンドラを直接実行するわけではないこと。
・イベントハンドラの実行は、イベントが発生し、イベントループが処理を行なうことで初めて行われる。
・イベントリスナーは、イベントが発生するまで待機し、イベントが発生したときに初めてイベントハンドラを呼び出す役割を果たす。

イベントリスナーとイベントオブジェクトの関係

①イベントの発生
ユーザーがWebページ上で何らかのアクション(クリック、キー入力など)を行なうと、ブラウザはイベントを発生させる。

https://fuuno.net/nani/nani04/nani04.html より画像引用

②イベントオブジェクトの生成
ブラウザはイベントオブジェクトを生成する。

③イベントが発生すると、ブラウザはEventオブジェクトという特別なオブジェクトを生成する。このオブジェクトには、イベントに関する詳細な情報(イベントの種類、発生した要素、時刻など)が含まれている。
オブジェクトのイベントが発生すると、イベントハンドラが呼び出される。イベントハンドラは関数であり、この関数への参照が入力として渡される。このような関数への参照をコールバック(call back)と呼び、関数が入力として渡され、それが渡された関数によって呼び出されることを意味する。

https://fuuno.net/nani/nani04/nani04.html より画像引用

④イベントリスナーの実行
イベントリスナーは、特定のイベントが発生したときに実行される関数。
イベントリスナーが実行される際、ブラウザは生成されたEventオブジェクトを引数としてイベントリスナーに渡す。

⑤イベントオブジェクトへの問い合わせ
イベントリスナーは、渡されたEventオブジェクトのプロパティやメソッドを使用して、イベントに関する情報を取得したり、イベントの動作を制御したりする。

Eventオブジェクトが持つ主要なメンバ変数

target

イベントが発生した要素の参照を保持する。
例えば、ボタンがクリックされた場合、target はそのボタンの DOM 要素を指す。
イベントリスナーが複数の要素に登録されている場合に、どの要素でイベントが発生したかを特定するのに役立つ。

timestamp

イベントが発生した時刻をミリ秒単位で表す。
イベントの発生順序や、イベント間の時間間隔を計測するのに利用できる。
パフォーマンス計測や、イベントのタイミングに基づく処理などに役立つ。

type

発生したイベントの種類を表す文字列を保持する。
例えば、click、mouseover、keydown などがある。
イベントリスナーが複数の種類のイベントに対応している場合に、どのイベントが発生したかを特定するのに使用する。

mabomabo

addEventListener メソッド

指定された要素にイベントリスナーを登録するために使用される、Event Targetインターフェースを実装したHTML要素(オブジェクト)にあるメソッド。
特定の要素に対して、addEventListener メソッドを使用してイベントハンドラを登録することができる。
これにより、特定の要素に対して、ある種類のイベントが発生した時に、関数を実行することができるようになる。
※HTML要素はEventTargetインターフェースを実装している。

書式
addEventListener('type', callback, option)

・第一引数にはイベントの種類('click'、'mouseover' など)を指定する。
・第二引数にはイベントが発生したときに実行する関数を指定する。
・第三引数はオプションで、イベントリスナーの動作を制御するための設定を指定する。
※デフォルトで false が設定されている。
※オプション詳細については今回は割愛。

メソッドの共通性
addEventListenerメソッドは「イベントリスナーを登録する」という共通の機能を提供し、コールバック関数によって各要素の固有の処理を定義できるため、多様なイベント処理が可能になる。
addEventListenerメソッド自体は統一された機能を提供するが、各要素に登録されるイベントハンドラ(コールバック関数)が異なるため、結果として異なる処理が実行される。

イベントリスナーが設定された要素オブジェクトに、指定された関数の参照(メモリー上のアドレス)が渡される。
addEventListener() メソッドの第二引数に渡される関数は、実際には関数そのものではなく、その関数がメモリー上に格納されている場所のアドレス(参照)。
イベントが発生すると、ブラウザはこのアドレスを使用して、登録された関数を実行する。

onclick 属性

イベント発生時に実行するJavaScriptコードをHTMLの属性値として直接指定する。
1つの要素に対して1つのイベントハンドラしか登録できない。
onclick 属性を使うと、要素がクリックされたときに JavaScript が実行される。
onclick 属性を使用する場合、JavaScript のコードを HTML のタグに直接埋め込むことになるため、同じ JavaScript のコードを別のタグで再利用することができない。
そのため、同じコードを複数の場所で使用する場合は、全てのタグに同じコードを埋め込む必要がある。
この場合、コードの修正が必要な場合には全てのタグを修正する必要があるため、メンテナンスの手間が増えるというデメリットがある。
また、HTML内にJavaScriptコードを埋め込むため、記述が簡単だが、コードが煩雑になりやすいというデメリットがある。
※onclick 属性内のJavaScriptコードは、セミコロン (;) で区切られた処理が順番に実行される。セミコロンは、JavaScriptにおいてステートメント(文)の終わりを示す記号。

※addEventListener を使用する場合
同じイベントリスナーを複数の要素に適用することができるため、コードの再利用性が高くなる。

mabomabo

EventTarget インターフェース


https://www.stefanjudis.com/today-i-learned/how-to-use-eventtarget-as-a-web-native-event-emitter/ より画像引用

Web APIのインターフェースであり、イベントリスナーを登録できるオブジェクトが実装すべきインターフェース。
EventTargetを実装しているオブジェクトは、イベントを受け取り、それに応じた処理を行うことができるようになる。

EventTargetの重要性

EventTargetインターフェースは、Webページにおけるイベント処理の中核を担っている。
EventTargetを実装するオブジェクトは、イベントリスナーを登録することで、ユーザーの操作やブラウザの動作に応じて柔軟な処理を実行できる。

主なメソッド

EventTarget.addEventListener(eventType, callback):イベントリスナーの登録

ターゲットのオブジェクトにイベントリスナーを登録する。
イベントが発生するたびにコールバック関数が実行される。
addEventListener()メソッドを使用して、特定のイベントが発生したときに実行する関数(イベントハンドラ)を登録できる。

EventTarget.removeEventListener(eventType, callback):イベントリスナーの削除

eventType、およびコールバック関数の参照に基づいて、このオブジェクトからイベントリスナーを削除する。
removeEventListener()メソッドを使用して、登録したイベントリスナーを削除できる。

EventTarget.dispatchEvent(eventObject):イベントの発火

任意のイベントを作成し、dispatchEvent()メソッドを使って、対象となる要素にイベントを発生させることができる。
Eventオブジェクトには文字列であるeventTypeが含まれている。
dispatchEvent()メソッドを使用して、プログラムからイベントを発生させることができる。
※eventType は任意の文字列で、イベントが発火した際、Element オブジェクトがどのイベントか調べるために使用する。
※メソッドの引数はイベントオブジェクト

dispatchEvent() メソッドを呼び出すと、指定されたイベントタイプのイベントオブジェクトが作成され、対象の要素に送信される。
ブラウザは、このイベントオブジェクトを受け取り、対象の要素に登録されているイベントリスナーを検索する。
該当するイベントタイプのイベントリスナーが見つかれば、そのイベントリスナーが実行される。
※dispatchEvent() メソッドは、ユーザーの操作とは無関係に、プログラムから強制的にイベントを発生させることができる。

ブラウザは、全ての追跡可能なイベントに対して、要素の dispatchEvent メソッドが自動的に実行される。
(例)click イベントが発生すると、ブラウザは target.dispatchEvent(new Event("click")) を自動的に実行する

EventTargetを実装するオブジェクトの例

DOM要素(HTMLElementなど)
Webページの要素(ボタン、テキストボックスなど)は、ユーザーの操作に応じて様々なイベントを発生させる。

documentオブジェクト
HTMLドキュメント全体を表すオブジェクトで、ページの読み込み完了などのイベントを発生させる。

windowオブジェクト
ブラウザのウィンドウを表すオブジェクトで、ページのロードやリサイズなどのイベントを発生させる。

XMLHttpRequestオブジェクト
HTTPリクエストを行うためのオブジェクトで、リクエストの完了などのイベントを発生させる。


Event Capturing

https://blog.stackademic.com/understanding-javascript-events-and-propagation-in-the-dom-fabdf78ff825 より画像引用

ブラウザのDOMイベントは、ある要素で発生したイベントが親要素や祖先要素に伝わっていく流れが3段階に分かれている。

キャプチャリングフェーズ(捕捉段階)
→ 親から子へ向かってイベントが伝わる段階(ルート → 親 → 子)

ターゲットフェーズ(対象段階)
→ イベントが実際に発生した要素(ターゲット)に到達する段階

バブリングフェーズ(伝播段階)
→ 子から親へ向かってイベントが伝わる段階(子 → 親 → ルート)

イベントリスナー登録時のオプション
element.addEventListener('click', handler, useCapture);

・useCapture が true → キャプチャリングフェーズでイベントを捕捉
・useCapture が false または省略 → バブリングフェーズでイベントを捕捉

イベント捕捉タイミング

フェーズ イベント検知のタイミング
キャプチャリング イベントがターゲットに届く「前」に親要素で捕捉 親要素が先に反応する
バブリング イベントがターゲットで処理された「後」に親要素で捕捉 子要素が先に反応し、親要素が後から反応
const parent = document.getElementById('parent');
const child = document.getElementById('child');

// キャプチャリングフェーズで親にイベント登録
parent.addEventListener('click', () => {
  console.log('親がキャプチャリングフェーズで検知');
}, true); // true → キャプチャリング

// バブリングフェーズで親にイベント登録
parent.addEventListener('click', () => {
  console.log('親がバブリングフェーズで検知');
}, false); // false または省略 → バブリング

// 子要素のクリックイベント
child.addEventListener('click', () => {
  console.log('子要素がクリックされた');
});

// 出力結果
ボタン(子要素)をクリックすると ↓
1. 「親がキャプチャリングフェーズで検知」
2. 「子要素がクリックされた」
3. 「親がバブリングフェーズで検知」
mabomabo

JavaScriptによる処理の全体像

ブラウザの実行環境で JavaScript が処理される時に、次の1から7の順番で実行される。
① ブラウザ上で読み込まれた JavaScript が、JavaScript エンジンのコールスタック上で実行される
② コールスタック上で実行中に非同期関数が呼び出されると、非同期関数の引数として渡されたコールバック関数は Web APIs に送られる
③ Web APIs に送られたコールバック関数は、条件を満たすまでは Web APIs で待機する
④ Web APIs で待機しているコールバック関数は、条件を満たすとコールバックキューに追加される
⑤ コールバックキューに追加されたコールバック関数は、コールスタックで実行中の関数が空になるまで待機する
⑥ コールスタックが空になると、コールバックキューで待機しているコールバック関数は、イベントループによってコールスタックに追加される
⑦ コールスタックに追加されたコールバック関数が実行される

JavaScriptエンジン

一番左が JavaScript コードで、コードは1行目から解析され、スタック領域に順番を持った状態でメモリの割り当てが行なわれる。
オブジェクト、関数、配列の実体はヒープ領域にメモリの割り当てが行なわれ、それらの参照はスタック領域にメモリの割り当てが行なわれていることが概念的に示されている。
(age と name はスタック領域のみ割り当てられている)

コールスタック

コールスタックは、JavaScript ランタイム上で実行されている処理の履歴を格納する仕組みで、どの関数が現在実行されていて、その関数の中でどの関数が呼び出されたかをスタックのデータ構造で記録している。コールスタックに追加される関数は、LIFO 方式で処理される。


① グローバルコンテキストの実行が開始する (関数 b と a の実体はメモリ領域上のヒープ領域に割り当てられ、その参照がスタック領域に割り当てられる)
② 最後の行で関数 a が呼び出される → 関数 a がコールスタックにプッシュされる
③ 関数 a の内部で関数 b が呼び出される → 関数 b がコールスタックにプッシュされる
④ 関数 b の処理が終了する → 関数 b がコールスタックからポップされる
⑤ 関数 a の処理が終了する → 関数 a がコールスタックからポップされる
⑥ グローバルコンテキストの実行が終了する

Web APIs

Web APIs は、ブラウザによって提供されているウェブの API 群と、その実行環境を指す。
Web APIs で提供されている非同期関数を呼び出すと、コールバック関数が Web API Container に追加され、条件を満たすまで待機する。
コールバック関数は、待機している時点で実行されてなくて、条件を満たした時にコールバックキューに追加され、JavaScript エンジンが実行可能になったタイミングで JavaScript エンジンに渡され、実行される。

Web APIsにあるもの
・console
・setInterval, setTimeout
・Promise
・Fetch
・XMLHttpRequest
・DOM
・Mouse Event

非同期関数の実行(例:setTimeOut関数)

setTimeout 関数を定義したコードを実行すると、コールスタックに setTimout 関数が追加され、実行される。
この時点で、第1引数として渡された callback 関数の呼び出しは行なわれていなくて、関数リテラルとして Web API Container に追加され、待機する。
第2引数に1000を渡しているので、1000ミリ秒経過すると関数リテラルはコールバックキューに追加され、最終的に JavaScript エンジンに追加された後、実行される。

コールバックキューに追加された順に随時コールスタックに移され実行されるため、callback 関数は先に追加された関数の実行完了を待つ。
そのため、setTimeout 関数を用いた遅延処理は、1000ミリ秒後に確実に実行されることを保証しないので注意が必要。

このことから、setTimeout 関数は第2引数で指定したミリ秒後に第1引数で渡したコールバック関数をコールバックキューに追加する関数と言い換えることができる。

コールバックキュー

JavaScript エンジンは同時に1つの処理しか実行できません。JavaScript エンジンが処理をしている間、次に処理されるものが待機する場所がコールバックキューと呼ばれる。
コールバックキューのデータ構造は FIFO (First In, First Out)方式で、コールバックキューに格納した順に取り出しが行なわれる。

ブラウザの実行環境では、コールバックキューにタスクキューとマイクロタスクキューの2種類のキューがある。(Node.js ではこのコンポーネントをイベントキューと言い、仕組みとしては似ているが、コールバックキューと完全に同じではない)

図3のように簡略するとコールバックキューのみで処理しているように見えるが、より詳しい図にすると次のようになる。(図6)

タスク

タスクは、次の種類のコールバック関数を指し、タスクキューに格納される。
・script タグで読み込んだ JavaScript ファイル
・setTimeout / setInterval のコールバック関数
・UI イベント(クリック、マウス移動等)のコールバック関数
・requestAnimationFrame のコールバック関数
※タスクは、マクロタスク *Macrotaskと呼ばれることもある。

マイクロタスク

マイクロタスクは、次の種類のコールバック関数を指し、マイクロタスクキューに格納される。
・Promise の then / catch / finally のコールバック関数
・queueMicrotask のコールバック関数
・MutationObserver のコールバック関数
マイクロタスクはジョブ *Job、マイクロタスクキューはジョブキュー *Job queueと呼ばれることもある。

タスクキューとマイクロタスクキューの違い
タスクキューとマイクロタスクキューの違いは、JavaScript の1つの処理サイクル内で優先して実行されるかどうか。
具体的には1つの処理サイクル内で、1つのタスクキューが実行された後、マイクロタスクキューにあるマイクロタスクは、空になるまで全て実行される。タスクキューとマイクロタスクキューで実行された結果を受けて、最後にレンダーキューの描画タスクが実行される。

マイクロタスクキューは格納されている全てのコールバック関数が終わるまで処理が行われるため、Promise.then 等の連結された大量の処理があると、それらのタスクの処理が終わるまで他のタスクの実行が行われない。

Event loop (イベントループ)

イベントループは、コールスタックが空になる度にコールバックキューからタスク(コールバック関数)を取り出し、JavaScript エンジンのコールスタックに追加する。

・コールバックキューに実行待ちの関数があるかどうか
・コールスタックに実行中の関数がないかどうか
イベントループはこの2つの条件が満たされるのを待ち、満たされた時にコールバックキューの実行待ちの関数をコールスタックに追加するという処理を無限に繰り返す。

まとめ

JavaScript エンジンは、コールスタックとメモリ領域という2つの概念の理解が必要になる。
JavaScript が評価され、メモリ上に展開され、コールスタックで実行される。
Web APIs から提供されている API を呼び出すと、Web APIs の実行環境で処理が実行される。その時に非同期関数の呼び出しの場合、Web APIs の実行環境内で、条件を満たすまで待機する。
そして、条件を満たすと、コールバックキュー内のタスクキュー、もしくはマイクロタスクキューのいずれかのキューに格納される。
コールバックキューに格納されたタスクはコールスタックで処理可能になるまで待機する。
コールスタックが処理中かどうかはイベントループによって監視され、コールスタックが空になるとキューで待機している先頭のタスクから取り出され、コールスタックで実行される。

https://engineering.mercari.com/blog/entry/20220128-3a0922eaa4/ より画像・テキスト引用し、一部編集

mabomabo

Webブラウザの仕組み


https://qiita.com/megmogmog1965/items/e180d02be711cecdc038 より画像引用

JavaScript コンテキストと Web API

ブラウザのウインドウやタブでページ遷移 (URL の変更) が発生すると、レンダリングエンジンから JavaScript エンジンへ 新しい JavaScript コンテキストの生成 が指示される。

HTML 中に含まれる <script> タグで入力したユーザー JavaScript は、この生成されたコンテキスト上に取り込まれ実行される。


https://qiita.com/megmogmog1965/items/e180d02be711cecdc038 より画像引用

mabomabo

グローバルコンテキストとコールスタック

JavaScriptエンジンは、スクリプトの実行を開始すると、最初にグローバル実行コンテキストを作成する。
・このグローバルコンテキストは、コールスタックの一番下に配置される。
・グローバルコンテキストは、スクリプト全体の実行環境を提供し、グローバルスコープで宣言された変数や関数を管理する。
・グローバルコンテキストは、スクリプトの実行が終了するまでコールスタックに残り続ける。

ホイスティング(Hoisting)
コンテキスト内で宣言した変数や関数の定義を、コード実行前にメモリーに配置すること。
このホイスティングは、コンテキストが生成されるごとに行われる。
コンテキストが生成されるごとに、ホイスティングが発生し、宣言がメモリスペースに展開されている。

https://zenn.dev/airiswim/articles/1643db802618de より画像引用

mabomabo

実行コンテキスト(Execution Context)

実行コンテキストとは、JavaScriptコードが実行される環境のこと。
実行コンテキストは、変数、関数、thisキーワードなどのスコープを決定し、コードの実行に必要な情報を提供します。
JavaScriptエンジンは、コードの実行前に実行コンテキストを作成し、コードの実行中に必要に応じて更新します。

実行コンテキストの主な要素

変数環境
変数と関数の宣言、およびそれらのレキシカルスコープを記録します。

レキシカル環境
スコープチェーンを管理し、変数と関数の参照を解決します。

this値
thisキーワードの値を決定します。

実行コンテキストの種類

実行コンテキストはグローバルコンテキスト、関数コンテキスト、 Evalコンテキスト(eval関数は非推奨のため割愛する)の3種類があり、まずはグローバルコンテキストが作成され、それを実行中に関数コンテキストが作成されて行きます。

グローバル実行コンテキスト
Webブラウザでは、最初にHTML文書が読み込まれたときに作成されます。
グローバルスコープ内の変数と関数を定義します。
グローバルコンテキスト(Global Context) はコードを実行する前に生成される、関数の内部以外の環境であり、トップレベルのコードが実行されます。「実行中のコンテキスト内の変数、関数」、「グローバルオブジェクト」、「this」が使用可能となります。

関数実行コンテキスト
関数が呼び出されたときに作成されます。
関数内のローカル変数と関数を定義します。
関数コンテキスト(Function Context) は関数が実行される直前に生成される、関数内の環境です。「実行中のコンテキスト内の変数、関数」、「arguments」、「super」(特殊な環境のみ)、「this」、「外部の値(関数外での値)」が使用可能となります。

Eval実行コンテキスト
eval()関数が呼び出されたときに作成されます。
eval()関数内で実行されるコードのスコープを定義します。

JavaScriptエンジンの役割

JavaScriptエンジンは、コードの実行前に実行コンテキストを作成し、コードの実行中に必要に応じて更新します。
実行コンテキストは、コードの実行に必要な情報を提供し、コードの動作を制御します。
JavaScriptエンジンは、実行コンテキストを管理することで、JavaScriptコードのスコープ、thisキーワード、およびその他の実行環境を制御します。

***
JavaScriptも他のプログラミング言語と同じで、実行時にコードを1つ1つ読んでいくことに変わりはないのですが、JavaScriptはコードを実行コンテキストと呼ばれる実行環境に大きく分けて実行します。
実行コンテキストはコード実行中にその都度作成され、コールスタックに積まれて行きます。JavaScriptはコールスタックに積まれた実行コンテキスト単位で処理を実行していきます。
そして、実行コンテキストごとにその中のコードが実行されるので、結局は全てのコードが実行されることとなります。また、この一連の流れをスレッド(Thread) と呼びます。

最初に必ずグローバルコンテキストが生成されます。
グローバルコンテキストはそのプログラムの設計図のようなもので、上から順番に実行されていきます。そして途中で関数を実行する記述(関数の定義ではない)があると、関数コンテキストが作成されます。するとグローバルコンテキストの処理はいったん中断され、その関数コンテキストを元に関数が実行されます。関数の中にさらに関数の実行があれば、その関数のコンテキストが作成されます。そして関数の実行が終われば、グローバルコンテキストの処理に戻ります。


https://zenn.dev/antez/books/568dd4d86562a1/viewer/8de90b より画像引用

mabomabo

JavaScriptエンジンの処理の流れ

解析(Parsing)

JavaScriptエンジンは、まずソースコードを解析し、抽象構文木(AST: Abstract Syntax Tree)と呼ばれるデータ構造を生成します。
ASTは、ソースコードの構造を木構造で表現したもので、JavaScriptエンジンがコードを理解するための基礎となります。

コンパイル(Compilation)

次に、JavaScriptエンジンはASTを元に、実行可能なコード(バイトコードや機械語など)を生成します。
このプロセスは、JIT(Just-In-Time)コンパイルと呼ばれる技術が使われることが多く、実行時にコードを最適化することでパフォーマンスを向上させます。

実行コンテキストの生成

コードの実行前に、JavaScriptエンジンは実行コンテキストを生成します。
実行コンテキストは、変数環境、レキシカル環境、this値などの情報を含み、コードの実行環境を提供します。
この実行コンテキストは、ASTやコンパイルされたコードに基づいて生成されます。
実行コンテキストは、コードの実行に必要な情報(変数のスコープ、thisの値など)を提供し、コードの実行環境を確立します。
※実行コンテキストがない状態では、JavaScriptエンジンはコードを実行するための情報が不足しているため、コードを実行できません。

コードの実行

最後に、JavaScriptエンジンは実行コンテキスト内でコードを実行します。
コードの実行中に、必要に応じて実行コンテキストが更新されます。

実行コンテキストの役割

・実行コンテキストは、ソースコードを直接表現するものではなく、コードが実行される環境を抽象化したものです。
実行コンテキストは、以下の情報を提供します。
・変数のスコープ
・関数のスコープ
・thisキーワードの値
・その他、コードの実行に必要な情報

mabomabo

イベントループの役割

JavaScriptはシングルスレッドで動作するため、複数の処理を同時に実行できません。
イベントループは、非同期処理(例えば、HTTPリクエストやタイマー処理など)の結果を効率的に処理し、JavaScriptのシングルスレッドの制約を克服するための仕組みです。

イベントループの構成要素


https://engineering.mercari.com/blog/entry/20220128-3a0922eaa4/ より画像引用

コールスタック

同期的な処理が実行される場所です。

イベントキュー

上記の図でいうタスクキューのこと。
非同期処理の結果やイベントが発生した際に、コールバック関数が追加される場所です。

Web API

ブラウザが提供する非同期処理を行うためのAPIです。

イベントループの動作

JavaScriptエンジンは、コールスタック内のコードを上から順に実行します。
非同期処理が実行されると、Web APIに処理が委譲され、Web APIは処理が完了するとコールバック関数をイベントキューに追加します。
コールスタックが空になると、イベントループはイベントキューからコールバック関数を取り出し、コールスタックにプッシュして実行します。
この処理を繰り返すことで、JavaScriptは非同期処理を効率的に実行できます。

ブラウザとJavaScriptエンジンの関係

イベントループは、ブラウザのWeb APIとJavaScriptエンジンが連携して動作する仕組みです。
Web APIは、非同期処理を行い、その結果をイベントキューに追加する役割を担います。
JavaScriptエンジンは、イベントループを通じてイベントキューを監視し、コールバック関数を実行する役割を担います。

イベントと実行コンテキスト

JavaScriptエンジンは、コードを実行する際に必ず実行コンテキストを作成します。
イベントハンドラもJavaScriptの関数であるため、イベントが発生し、イベントハンドラが呼び出される際には、そのイベントハンドラのための実行コンテキストが作成されます。
したがって、イベントごとに(正確にはイベントハンドラの実行ごとに)実行コンテキストが生成されると言えます。

イベントハンドラ

① Web APIによって追加された非同期処理の結果であるコールバック関数
setTimeoutやfetchなどの非同期処理が完了した際に実行されるコールバック関数。
② ユーザーの操作やブラウザのイベントに対応するコールバック関数
clickイベントやloadイベントなど、ユーザーの操作やブラウザのイベントに対応するために登録されたコールバック関数。

イベントループと実行コンテキスト

イベントループは、イベントキューからコールバック関数(イベントハンドラ)を取り出し、コールスタックにプッシュして実行します。
このとき、イベントハンドラのための実行コンテキストが作成され、コールスタックに積まれます。
イベントハンドラの実行が終了すると、その実行コンテキストはコールスタックからポップされ、破棄されます。

イベントオブジェクトのライフサイクル

イベントの発生とイベントオブジェクトの生成
ブラウザのレンダリングエンジンなどがイベントを検出し、イベントオブジェクトを生成します。

イベントキューへの追加
イベントオブジェクトに関する情報と、それに対応するイベントハンドラ(コールバック関数)が、Web APIを通じてイベントキューに追加されます。

イベントループによる処理
イベントループは、コールスタックが空になった時点で、イベントキューからイベントハンドラを取り出し、コールスタックにプッシュして実行します。

イベントハンドラへの引数渡し
イベントハンドラが実行される際に、イベントオブジェクトが引数として渡されます。

イベントハンドラの実行とオブジェクトの利用
イベントハンドラ内で、イベントオブジェクトのプロパティ(イベントの種類、発生源の要素、座標など)を使用して、必要な処理を実行します。

オブジェクトの破棄
イベントハンドラの実行が終了すると、イベントオブジェクトは不要になるため、JavaScriptエンジンによって破棄されます。

重要なポイント

・イベントオブジェクトは、イベントハンドラが実行される間だけ有効なオブジェクトです。
・イベントハンドラの実行が終了すると、イベントオブジェクトはメモリから解放されます。
・イベントキューには、イベントオブジェクト自体が直接格納されるのではなく、イベントハンドラがイベントオブジェクトにアクセスするための情報が格納されます。

mabomabo

Web APIの役割

Web APIは、JavaScriptコードからブラウザの機能を利用するためのインターフェースを提供します。
addEventListener()メソッドは、Web APIの一部であり、イベントリスナーの登録をブラウザに依頼します。
ブラウザは、Web APIを通じて受け取った情報を元に、イベントが発生したときに実行すべきイベントハンドラを内部的に管理します。

mabomabo

イベントに対する処理の設定方法には以下のようなものがあります。
・HTML 要素の属性に指定(イベントハンドラ)※非推奨
・DOM 要素のプロパティに指定(イベントハンドラ)
・EventTarget.addEventListener() を利用(イベントリスナ)

クライアントサイド JavaScript ではイベントとイベントハンドラの関連付けを行う方法
・タグ内属性として宣言
・JavaScript コード内で宣言
・addEventListener/attachEvent メソッドを利用

mabomabo

ブラウザのイベント認識の仕組み

イベントの組み込み

ブラウザは、W3C(World Wide Web Consortium)などの標準化団体によって定められた仕様に基づいて、様々なイベントの種類を内部に組み込んでいます。
これらのイベントは、ユーザーの操作(クリック、キー入力など)やブラウザの状態変化(ページの読み込み完了など)に対応します。

イベントの検出

ブラウザのレンダリングエンジンやUIコンポーネントは、ハードウェアからの割り込みやOSからのイベント通知などを利用して、イベントを検出します。
例えば、マウスのクリックイベントは、マウスドライバーからの通知によって検出されます。

イベントオブジェクトの生成

イベントが検出されると、ブラウザはイベントに関する情報を持つイベントオブジェクトを生成します。
イベントオブジェクトには、イベントの種類、発生源の要素、発生時刻などの情報が含まれます。

イベントリスナーの登録

JavaScriptコード内でaddEventListener()メソッドが呼び出されると、Web APIを通じてイベントリスナーが登録されます。
イベントリスナーは、特定のイベントが発生したときに、どの関数(イベントハンドラ)を実行するかをブラウザに伝えます。

イベントハンドラの実行

イベントが発生し、イベントリスナーが登録されていれば、ブラウザはイベントハンドラを呼び出し、イベントオブジェクトを引数として渡します。
イベントハンドラ内で、イベントオブジェクトの情報を使用して、必要な処理を実行します。

JavaScriptエンジンとWeb APIの役割

JavaScriptエンジン

JavaScriptコードを実行し、Web APIを通じてブラウザの機能にアクセスします。
イベントハンドラとして登録されたJavaScriptコードを実行します。

Web API

ブラウザが提供する機能群であり、JavaScriptコードからブラウザの機能を利用するためのインターフェースを提供します。
イベントリスナーの登録、イベントキューへのタスク追加などの役割を担います。

mabomabo

"HTML要素"と"HTML要素のオブジェクト" の違い

HTML要素

・HTML要素は、HTMLドキュメントを構成する基本的な要素。
・<p>(段落)、<h1>(見出し)、<div>(汎用コンテナ)などのタグによって定義される。
・HTML要素は、Webページの構造と内容を定義するために使用される。
例えば、<p>これは段落です。</p>というHTMLコードは、「これは段落です。」というテキストを表示する段落要素を定義します。

HTML要素のオブジェクト

・HTML要素のオブジェクトは、JavaScriptなどのプログラミング言語からHTML要素を操作するための表現。
・DOM(Document Object Model)と呼ばれるWebページの構造を表現するモデルにおいて、HTML要素はオブジェクトとして表現される。
・これらのオブジェクトは、プロパティ(属性)とメソッド(関数)を持ち、JavaScriptからアクセスして操作できる。
例えば、JavaScriptからdocument.getElementById("myParagraph")というコードを使用して、IDが"myParagraph"の段落要素のオブジェクトを取得し、そのtextContentプロパティを変更してテキスト内容を変更したり、styleプロパティを変更してスタイルを変更したりできます。

具体的な違い

HTML要素

Webページのソースコードに記述されるタグとその内容。
Webページの構造と内容を定義する静的なもの。

HTML要素のオブジェクト

JavaScriptからHTML要素を操作するための動的な表現。
DOMによって生成され、プロパティとメソッドを持つ。

mabomabo

ブラウザイベント

マウスイベント

イベントタイプ いつ発生するか
click 要素がカーソルでクリックされた時
mousedown マウスボタンが押された時
mouseup マウスボタンが(押された状態から)解放された時
mousemove カーソルが要素の上に動かされた時
mouseleave カーソルが要素を離れた時
wheel マウスのホイールが動かされた時

キーボードイベント

イベントタイプ いつ発生するか
keydown 任意のキーが押された時
keyup 任意のキーが(押された状態から)解放された時
keypress Shift、Fn、CapsLockを除く任意のキーが押された状態にある時(連続して発生する)

タッチイベント

イベントタイプ いつ発生するか
touchstart スクリーンデバイスで要素がタッチされている時
touchend 要素がタッチされた状態から解放された時

ドラッグ&ドロップイベント

イベントタイプ いつ発生するか
drop 要素が有効なドロップ対象にドロップされた時
drag 要素もしくは選択文字列がドラッグされている間。(350ミリ秒ごとに断続的に発火します)

ビューイベント

イベントタイプ いつ発生するか
resize ドキュメントビューがリサイズされた時
fullscreenchange ドキュメントがフルスクリーンにセットされた時
scroll ドキュメントがスクロールされた時

フォーカスイベント

イベントタイプ いつ発生するか
focus 要素がフォーカスを受け取った時(例:テキスト入力がフォーカスされており、入力される時)
blur フォーカスが失われた時

CSS アニメーション/トランジション

イベントタイプ いつ発生するか
animationstart 要素のアニメーションが開始した時
animationend 要素のアニメーションが終了した時
transitionstart 要素のトランジションが開始した時
transitionend 要素のトランジションが終了した時
mabomabo

querySelectorAll


https://www.r-staffing.co.jp/engineer/entry/20201113_1 より画像引用

要素ノードには、element.querySelectorAll メソッドがある。
このメソッドは、例えば "#target1"、".container"、"div p span.icon-span" などの CSS セレクタを受け取り、該当するノードのリストを表す DOM NodeList オブジェクトを返す

具体的には、このメソッドは、要素を根ノードとする木の内部を検索し、セレクタ文字列にマッチする孫要素を全て検索し、マッチした全ての要素を DOM NodeList データ構造で表された要素のリストとして返す。これにより、複数の要素を一度に取得できる。

取得した NodeList は、インデックス演算子 [] または nodeList.item(index) メソッドを使用して、個々のノードにアクセスすることができる。ただし、querySelectorAll は NodeList を返すため、1 つの要素のみを取得する場合でも、インデックス 0 で取得しなければならない。

https://pikawaka.com/javascript/querySelectorAll より画像引用

mabomabo

classList プロパティ

classList プロパティは、特定の要素のクラス名(文字列)を追加したり削除したりすることができる。
add、remove、contains、toggle などのメソッドがある。

mabomabo

getAttribute メソッド

Web ページ上で HTML 要素を操作する場合に、JavaScript を使用してその属性値を取得するメソッド。
指定した属性の値を取得する。
data-***属性の値を取得するには、属性名をそのまま引数として渡す。
data属性はgetAttributeメソッドを使うことによって直接アクセスすることができる。

setAttribute(属性名, 属性値)