今日、面接官がこんな質問をしました:onclick
と addEventListener
にはどんな違いがありますか?
とても良い質問で、自分の答えにはあまり満足できなかったので、後で自分で『赤い本』第 17 章のイベントと MDN を調べて、だいたいどういうことか理解しました。
まずは答えを提示します:
違い#
addEventListener()
は W3C DOM 規格で提供されるイベントリスナーを登録するためのメソッドです。その利点には以下が含まれます:
- 1 つのイベントに複数のリスナーを登録できる
- 特に AJAX ライブラリ、JavaScript モジュール、または他のサードパーティライブラリ / プラグインを必要とするコードで
listener
のトリガー段階をより細かく制御する手段を提供します(キャプチャまたはバブリングを選択可能)- 任意の DOM 要素に対して有効であり、HTML 要素だけに限られません。
- 登録したイベントは
removeEventListener
を使って削除できます- つまり、匿名イベント関数を追加した場合は削除できません
this
の値はイベントをトリガーした要素の参照ですconsole.log(e.currentTarget === this) // true
一方、onclick
は リスナーを登録する古い方法です。
- このメソッドは、その要素上のすべての既存の onclick イベントを置き換えます。他の on イベントも同様です。
- バブリングの制御などを細かく行うことはできません
- 削除は onclick イベントを null に置き換えることで行います
互換性#
addEventListener
は DOM 2 Events 規格で導入されました。
- IE 9 以前では、
addEventListener
の代わりにattachEvent
を使用する必要があります。 attachEvent
メソッドには欠点があり、this
の値はトリガーされた要素の参照ではなく、window
オブジェクトの参照になります。
一方、onclick
は DOM 0 規格の基本的な内容です。
- ほぼすべてのブラウザがサポートしており、特別なクロスブラウザ互換コードは必要ありません。
- したがって、通常このメソッドは動的にイベントハンドラーを登録するために使用されます。特別な機能を提供するために
addEventListener()
を使用する必要がない限り。
addEventListener#
EventTarget.addEventListener() メソッドは、指定されたリスナーを
EventTarget
に登録します。このオブジェクトが指定されたイベントをトリガーすると、指定されたコールバック関数が実行されます。イベントターゲットは、文書内の要素Element
、Document
、およびWindow
またはイベントをサポートする他の任意のオブジェクト(例えばXMLHttpRequest
)です。
addEventListener()
の動作原理は、EventListener
を実装した関数またはオブジェクトを、呼び出したEventTarget
の指定されたイベントタイプのイベントリスナーリストに追加することです。
addEventListener
には 3 つのパラメータ、type
、listener
、および useCapture
があり、最初のパラメータは イベントタイプ(click
、mousemove
など)、2 番目のパラメータはイベントのコールバック関数、3 番目のパラメータは listener
属性に関するオプションのパラメータオブジェクトです。注意すべき点は:
古いバージョンの DOM の規定では、
addEventListener()
の 3 番目のパラメータは、イベント処理プログラムをキャプチャ段階で呼び出すかどうかを示すブール値でした。時間が経つにつれて、より多くのオプションが必要であることが明らかになりました。メソッド内にさらに多くのパラメータを追加するよりも(オプションの値を渡すのは非常に複雑になります)、3 番目のパラメータをさまざまな属性を含むオブジェクトに変更する方が良いです。
古いバージョンのブラウザ(および比較的古くないブラウザ)では、3 番目のパラメータがブール値であると仮定しているため、この状況を効果的に処理するためにいくつかのコードを書く必要があります。興味のある各 options 値に対して機能検出を行うことができます。
つまり、addEventListener
は古いブラウザとの互換性を保つために追加のコードが必要ですが、onclick は必要なく、互換性を考慮するシナリオではよく考える必要があります。
イベント内の this#
addEventListener
の this
の値は通常、イベントをトリガーした要素の参照です。
- console.log(e.currentTarget === this) // true
しかし、アロー関数は異なります。アロー関数には独自の this がありません、アロー関数は自分のスコープチェーンの上のレベルから this
を継承します。
onclick#
グローバルイベントハンドラー(
GlobalEventHandlers
)のonclick
属性は、現在の要素のclick
イベントを処理するイベントハンドラー(event handler)です。
ユーザーが要素をクリックすると、
click
イベントがトリガーされます。各クリックの全過程において、click
イベントの実行順序はmousedown
とmouseup
イベントの後にあります。
注:
click
イベントを使用してアクションをトリガーする場合、マウスやタッチスクリーンを使用しないユーザーが同じ操作を行えるように、keydown
イベントにもこのアクションを追加することを考慮してください。
MDN の説明はそれほど詳細ではなく、『赤い本』の onclick に関する説明には非常に重要な点がいくつかあります。
スコープチェーンの拡張#
<script>
function showMessage() {
console.log("Hello world!");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()"/>
上記のようにonclick
で指定されたイベントハンドラーは、プロパティの値をカプセル化する関数を作成します。(ただし、これは推奨されません、理由は最後に述べます)
この関数には特別なローカル変数 event
があり、その中に event
オブジェクトが保存されています。
<!-- "click" を出力 -->
<input type="button" value="Click Me" onclick="console.log(event.type)">
このオブジェクトがあれば、開発者は別の変数を定義する必要がなく、ラッピング関数の引数リストから取得する必要もありません。この関数内での this
の値は、イベントのターゲット要素に相当します。以下の例のように:
<!-- "Click Me" を出力 -->
<input type="button" value="Click Me" onclick="console.log(this.value)">
この動的に作成されたラッピング関数には、特に面白い点があります。それはそのスコープチェーンが拡張されていることです。この関数内では、 document
と要素自身のメンバーがローカル変数としてアクセスできます。これは with
を使用して実現されています:
function() {
with(document) {
with(this) {
// プロパティ値
}
}
}
これが、onclick で定義されたイベントが、document 上のイベントを呼び出す際に document プレフィックスを省略できる理由です。実際には、前にもう一層のラッピングが行われています。
HTML で onclick イベントハンドラーを指定することの 1 つの問題は、HTML と JavaScript が強く結合されることです。イベントハンドラーを変更する必要がある場合、HTML と JavaScript の両方で変更を行う必要があります。HTML イベントハンドラーを使用することは推奨されず、JavaScript でイベントハンドラーを指定することが主な理由です。
つまり、JavaScript で本当に onclick を使用する必要がある場合、次のように使用します:
let btn = document.getElementById("myBtn");
btn.onclick = function(e) {
console.log(e.type); // "click"
};