e.currentTarget
はイベントリスナーを追加した要素で、e.target
はイベントが発生した要素である。"current"と付くせいで、一見すると名称を逆にした方が直感的に思える。というか私にはそう思え、いつも盲滅法に使って間違える。なんとかして矯正したい。
というわけで由来を調べてみたら、意外とあっさり誤解を上書きできる解釈が見つかった。私の理解とは順番が逆だったのだ。currentTarget
に"current"とつく理由は、イベントの方が先にあって、イベントリスナーを要素に後付けするからだ。
イベント伝播
Webページ上では、さまざまなイベントが発生する。これらのイベントの大部分は、その影響範囲が流動的である。HTMLは入れ子によってテキストを構造化するからだ。
例えばクリックイベントは、ボタンで捕まえたいケースもあればドキュメント全体で捕まえたいケースもある。これらを包括的に処理できる仕組みが求められる。
要素が入れ子になっているのだから、イベントが発生した末端の要素から親の要素に伝えていけば、すべての要素でイベントを捕まえることができる。これは、そのままイベント伝播=Event Propagation
という名称で概念化された(「でんぱん」じゃないよ「でんぱ」だよ⚡️)。
イベントプロパゲーションは、大きく3つの過程からなる。キャプチャリングフェーズとターゲットフェーズ、バブリングフェーズである。
キャプチャリングフェーズ
ルート要素から発火要素へと降っているフェーズを指す。発火したイベントを捕まえにいくイメージから(たぶん)、この過程はイベントキャプチャー(あるいはイベントキャプチャリング)と呼ばれる。
もしルート要素と発火要素の間に、キャプチャリングフェーズを対象としたイベントリスナーが指定されていれば、そのリスナーにはバブリングフェーズのイベントオブジェクトが渡される。
addEventListener
は、デフォルトでは後述のバブリングフェーズ、あるいはターゲットフェーズを対象にイベントリスナーを追加する。キャプチャリングフェーズでイベントを捕捉するには、第3引数のオプションオブジェクトに{ capture: true }
が指定されている必要がある。
ターゲットフェーズ
文字通りイベントが発火した時点に到達したフェーズを指す。発火元の要素にイベントリスナーが追加されていれば、そのイベントリスナーにはターゲットフェーズのイベントオブジェクトが渡される。
バブリングフェーズ
発火要素からルート要素へと遡っているフェーズを指す。泡のようにイベント情報が浮き上がっていくことから、この過程はイベントバブリングと表現される。
もしイベントが発火した要素とルート要素の間にある要素にイベントリスナーが指定されていれば、そのリスナーにはバブリングフェーズのイベントオブジェクトが渡される。
すべてのイベントがバブリングするわけではない。focus
イベントなど、境界がはっきりしているイベントはバブリングしない。ただ、このイベントバブリングは、DOMのイベントハンドリングを理解する上で非常に重要な概念である。
イベント伝播を確かめるサンプル
以下は、イベント伝播の挙動を確かめる簡単なサンプルである。「Click me」ボタンを押下すると、イベントリスナーの追加順ではなく、先に説明したフェーズの順に各イベントリスナーが実行されていることがわかるかと思う。
currentTargetとtarget
そのページの要素にイベントリスナーが登録されていようといまいと、ブラウザは3つのフェーズに沿ってイベントを発火する。イベントのtarget
は、もちろんイベントが発火した要素である。
addEventListener
によって要素にイベントリスナーが追加されると、その要素はイベントが伝播している過程の"現在"の要素として捉えられる。そのためイベントのcurrentTarget
は、イベントリスナーが追加された要素である。
これでもう間違えないぞ。