EventTarget.addEventListener()
EventTarget
の addEventListener()
メソッドは、特定のイベントが対象に配信されるたびに呼び出される関数を設定します。 対象としてよくあるものは Element
, Document
, Window
ですが、イベントに対応したあらゆるオブジェクトが対象になることができます (XMLHttpRequest
など)。
addEventListener()
は関数または EventListener
を実装したオブジェクトを、呼び出される EventTarget
における指定されたイベント種別のイベントリスナーのリストに加えることで動作します。
構文
target.addEventListener(type, listener [, options]); target.addEventListener(type, listener [, useCapture]); target.addEventListener(type, listener [, useCapture, wantsUntrusted ]); // Gecko/Mozilla only
引数
type
- 対象とするイベントの種類を表す文字列
listener
- 指定された型のイベントが発生するときに通知 (
Event
インターフェースを実装しているオブジェクト) を受け取るオブジェクト。これは、EventListener
インタフェースを実装するオブジェクト、あるいは、単純に、JavaScript の関数でなければなりません。コールバックについて詳しくは、イベントリスナーのコールバックを参照してください。 options
省略可- 対象のイベントリスナーの特性を指定する、オプションのオブジェクトです。次のオプションが使用できます。
capture
Boolean
値で、この型のイベントが DOM ツリーで下に位置するEventTarget
に配信される前に、登録されたlistener
に配信されることを示します。once
Boolean
値で、listener
の呼び出しを一回のみのとしたいかどうかを値で指定します。true
を指定すると、listener
は一度実行された時に自動的に削除されます。passive
Boolean
値で、true
ならば、listener
で指定された関数がpreventDefault()
を呼び出さないことを示します。呼び出されたリスナーがpreventDefault()
を呼び出すと、ユーザーエージェントは何もせず、コンソールに警告を出力します。詳細はパッシブリスナーによるスクロール性能の改善をご覧ください。-
mozSystemGroup
Boolean
値で、リスナーをシステムグループに追加するべきであることを示します。コードが XBL または Firefox ブラウザーの chrome で実行されている場合のみ利用できます。
useCapture
省略可Boolean
値で、この型のイベントが、DOM ツリー内の下のEventTarget
に配信される前に、登録されたlistener
に配信されるかどうかを示します。ツリーを上方向にバブリングしているイベントは、キャプチャーを使用するように指定されたリスナーを起動しません。イベントのバブリングとキャプチャーは、両方の要素がそのイベントのハンドラーを登録している場合に、別の要素内に入れ子になっている要素で発生するイベントを伝播する 2 つの方法です。イベント伝播モードは、要素がイベントを受け取る順番を決定します。詳細な説明は DOM Level 3 Events と JavaScript Event order を参照してください。指定されていない場合、useCapture
は既定でfalse
となります。-
注: イベントターゲットに登録されたイベントリスナーは、捕捉フェーズやバブリングフェーズではなく、ターゲットフェーズのイベントになります。ターゲットフェーズのイベントは、
useCapture
引数にかかわらず、すべてのリスナーを追加された順序で実行します。注:useCapture
はどんなブラウザーでもオプションになっているわけではありません。完全で最大限の互換性を得たいなら、引数を指定するようにしてください。 wantsUntrusted
true
の場合、このリスナーはウェブコンテンツによって発火された合成イベント (カスタムイベント) を受け取ります (ブラウザーのクロームでは既定でfalse
ですが、一般のウェブページではtrue
です)。この引数は Gecko でのみ利用可能であり、主にアドオンやブラウザー自身の役に立つものです。
返値
undefined
使用方法のメモ
イベントリスナーのコールバック
イベントリスナーには、コールバック関数を指定することもできますが、 EventListener
を実装したオブジェクトを指定することもでき、その場合は handleEvent()
(en-US) メソッドがコールバック関数として機能します。
コールバック関数自体は、 handleEvent()
メソッドと同じ引数と返値を持ちます。つまり、コールバック関数は発生したイベントを説明する Event
に基づいたオブジェクトを唯一の引数として受け付け、何も返しません。
たとえば、次のイベントハンドラーコールバックは、 fullscreenchange
および fullscreenerror
の両方を処理するために使用することができます。
function eventHandler(event) {
if (event.type == 'fullscreenchange') {
/* handle a full screen toggle */
} else /* fullscreenerror */ {
/* handle a full screen toggle error */
}
}
オプションの対応の安全な検出
DOM 仕様書の古い版では、 addEventListener()
の第 3 引数はキャプチャーを使用するかどうかを示す論理値でした。時間の経過とともに、より多くのオプションが必要であることが明らかになりました。関数にさらに多くの引数を追加する (オプションの値を扱うときに非常に複雑になります) のではなく、第 3 引数は、イベントリスナーを削除する過程を設定するためのオプションの値を定義するさまざまなプロパティを含むことができるオブジェクトに変更されました。
古いブラウザーは (あまり古くないブラウザーも含めて) 第 3 引数がまだ論理であると仮定しているので、このシナリオをインテリジェントに処理できるようにコードを構築する必要があります。これを行うには、興味のあるオプションごとに機能検出を使用します。
例えば、 passive
オプションをチェックしたい場合は次のようにします。
let passiveSupported = false;
try {
const options = {
get passive() { // この関数はブラウザーが passive プロパティに
// アクセスしようとしたときに呼び出されます。
passiveSupported = true;
return false;
}
};
window.addEventListener("test", null, options);
window.removeEventListener("test", null, options);
} catch(err) {
passiveSupported = false;
}
これは、 options
オブジェクトを生成し、 passive
プロパティのゲッター関数を持たせます。ゲッターは、呼ばれた場合に passiveSupported
フラグを true
に設定します。つまり、ブラウザーが passive
プロパティの値を options
オブジェクトでチェックした場合、 passiveSupported
は true
に設定され、そうでなければ false
のままになります。次に addEventListener()
を呼び出して、これらのオプションを指定して偽のイベント・ハンドラーをセットアップし、ブラウザーが第 3 引数としてオブジェクトを認識した場合にオプションがチェックされるようにします。その後、 removeEventListener()
を呼び出して、自分たちで後始末をします。 (呼ばれていないイベントリスナーでは handleEvent()
は無視されることに注意してください。)
この方法で、任意のオプションに対応しているかどうかを確認することができます。上に示したようなコードを使って、そのオプションのゲッターを追加するだけです。
そして、問題のオプションを使用する実際のイベントリスナーを作成したい場合は、次のようにします。
someElement.addEventListener("mouseup", handleMouseUp, passiveSupported
? { passive: true } : false);
ここでは、 mouseup
イベントのリスナーを someElement
要素に追加しています。第 3 引数の passiveSupported
が true
である場合、 options
オブジェクトを passive
を true
に設定して指定しています。そうでない場合は、論理値を渡す必要があることがわかっているので、 useCapture
引数の値として false
を渡しています。
ご希望であれば、 Modernizr や Detect It のようなサードパーティ製のライブラリを使用してこのテストを行うことができます。
Web Incubator Community Group の EventListenerOptions
の記事を参考にしてください。
例
シンプルなリスナーの追加
この例は、要素上でのマウスクリックを監視するための addEventListener()
の使い方を紹介します。
HTML
<table id="outside">
<tr><td id="t1">one</td></tr>
<tr><td id="t2">two</td></tr>
</table>
JavaScript
// t2 のコンテンツを変更する関数
function modifyText() {
const t2 = document.getElementById("t2");
if (t2.firstChild.nodeValue == "three") {
t2.firstChild.nodeValue = "two";
} else {
t2.firstChild.nodeValue = "three";
}
}
// イベントリスナーを table に追加
const el = document.getElementById("outside");
el.addEventListener("click", modifyText, false);
このコードの中で、 modifyText()
が addEventListener()
を使用して登録された click
イベントのリスナーです。表の中のどこかをクリックすると、ハンドラーに上がり、 modifyText()
を実行します。
結果
無名関数を使用したイベントリスナー
ここで、無名関数を使用してイベントリスナーに引数を渡す方法を見てみましょう。
HTML
<table id="outside">
<tr><td id="t1">one</td></tr>
<tr><td id="t2">two</td></tr>
</table>
JavaScript
// t2 のコンテンツを変更する関数
function modifyText(new_text) {
const t2 = document.getElementById("t2");
t2.firstChild.nodeValue = new_text;
}
// イベントリスナーを table に追加する関数
const el = document.getElementById("outside");
el.addEventListener("click", function(){modifyText("four")}, false);
なお、リスナーは実際にイベントに応答する modifyText()
関数に引数を送信することができるコードをカプセル化している無名関数であることに注意してください。
結果
アロー関数を使用したイベントリスナー
この例はアロー関数表記を使用して実装された、簡単なイベントリスナーを紹介しています。
HTML
<table id="outside">
<tr><td id="t1">one</td></tr>
<tr><td id="t2">two</td></tr>
</table>
JavaScript
// t2 の中身を変更するための関数
function modifyText(new_text) {
const t2 = document.getElementById("t2");
t2.firstChild.nodeValue = new_text;
}
// アロー関数で table にイベントリスナーを追加
const el = document.getElementById("outside");
el.addEventListener("click", () => { modifyText("four"); }, false);
結果
なお、無名関数とアロー関数は似ており、違いは this
のバインドです。無名関数 (及び伝統的なすべての JavaScript 関数) は自身の this
を作成するのに対し、アロー関数はそれを含む関数の this
を継承します。
つまり、アロー関数を使用したときは、それを含む関数の変数や定数をイベントハンドラーで利用することができます。
Example of options usage
HTML
<div class="outer">
outer, once & none-once
<div class="middle" target="_blank">
middle, capture & none-capture
<a class="inner1" href="https://www.mozilla.org" target="_blank">
inner1, passive & preventDefault(which is not allowed)
</a>
<a class="inner2" href="https://developer.mozilla.org/" target="_blank">
inner2, none-passive & preventDefault(not open new page)
</a>
</div>
</div>
CSS
.outer, .middle, .inner1, .inner2 {
display: block;
width: 520px;
padding: 15px;
margin: 15px;
text-decoration: none;
}
.outer {
border: 1px solid red;
color: red;
}
.middle {
border: 1px solid green;
color: green;
width: 460px;
}
.inner1, .inner2 {
border: 1px solid purple;
color: purple;
width: 400px;
}
JavaScript
const outer = document.querySelector('.outer');
const middle = document.querySelector('.middle');
const inner1 = document.querySelector('.inner1');
const inner2 = document.querySelector('.inner2');
const capture = {
capture : true
};
const noneCapture = {
capture : false
};
const once = {
once : true
};
const noneOnce = {
once : false
};
const passive = {
passive : true
};
const nonePassive = {
passive : false
};
outer.addEventListener('click', onceHandler, once);
outer.addEventListener('click', noneOnceHandler, noneOnce);
middle.addEventListener('click', captureHandler, capture);
middle.addEventListener('click', noneCaptureHandler, noneCapture);
inner1.addEventListener('click', passiveHandler, passive);
inner2.addEventListener('click', nonePassiveHandler, nonePassive);
function onceHandler(event) {
alert('outer, once');
}
function noneOnceHandler(event) {
alert('outer, none-once, default');
}
function captureHandler(event) {
//event.stopImmediatePropagation();
alert('middle, capture');
}
function noneCaptureHandler(event) {
alert('middle, none-capture, default');
}
function passiveHandler(event) {
// Unable to preventDefault inside passive event listener invocation.
event.preventDefault();
alert('inner1, passive, open new page');
}
function nonePassiveHandler(event) {
event.preventDefault();
//event.stopPropagation();
alert('inner2, none-passive, default, not open new page');
}
結果
外側、中央、内側のコンテナーをそれぞれクリックして、オプションがどのように動作するかを確認してください。
options
オブジェクトで特定の値を使用する前に、ユーザーのブラウザーがそれに対応していることを確認するのが良いでしょう。詳細はオプションの対応の安全な検出を参照してください。
その他の注
なぜ addEventListener() を使うのか
addEventListener()
は、 W3C DOM で仕様化されている、イベントリスナーを登録するための方法です。その利点は以下の通りです。
- イベントに複数のハンドラーを追加することができます。これは、特に、他のライブラリ/拡張で利用しても上手く動作する必要がある AJAX ライブラリや JavaScript モジュール、その他のライブラリや拡張機能と共に動作させる必要があるコードに役立ちます。
- リスナーがアクティブ化されたときに、その動きを細かく制御することを可能にします (キャプチャリング 対 バブリング)。
- HTML 要素だけでなく、任意の DOM 要素 で動作します。
別の方法である、イベントリスナーを登録するための古い方法 は、後で説明します。
イベント配信中のリスナーの追加
EventListener
がイベント処理中に EventTarget
に追加された場合、それが現在のアクションによって実行されることはありませんが、バブリングフェーズのように、後の段階のイベントフローで実行されるかもしれません。
複数の同一のイベントリスナー
複数の同一の EventListener
が、同じ EventTarget
に同じ引数で登録された場合、重複するインスタンスは反映されません。 EventListener
が 2 回呼び出されることはなく、重複するインスタンスは反映されないので、 removeEventListener()
で手動で削除する必要はありません。
ただし、無名関数をハンドラーとして使用する場合、そのようなリスナーは同じにはならないことに注意してください。無名関数はループ内であっても繰り返し呼び出されるだけで、同じソースコードを使って定義されていても同じにはならないためです。
ただし、このような場合に同じ名前の関数を繰り返し定義することは、より問題になる可能性があります (後述のメモリの問題を参照してください)。
ハンドラー内での "this" の値
一連の類似した要素に対して一般的なハンドラーを使いたい場合のように、イベントハンドラーが実行される要素を参照したいということがたびたびあります。
addEventListener()
を使って要素にハンドラー関数を設定したとき、ハンドラーの中の this
の値は要素への参照となります。これはハンドラーに渡された event 引数の currentTarget
プロパティの値と同じです。
my_element.addEventListener('click', function (e) {
console.log(this.className) // logs the className of my_element
console.log(e.currentTarget === this) // logs `true`
})
アロー関数は独自の this
コンテキストを持たないことをお忘れなく。
my_element.addEventListener('click', (e) => {
console.log(this.className) // WARNING: `this` is not `my_element`
console.log(e.currentTarget === this) // logs `false`
})
イベントハンドラー (例えば onclick
) が HTML ソース内の要素に指定されていた場合、属性値の Javascirpt コードは、 addEventListener()
を使用するような方法で this
の値をバインドしたハンドラー関数に置き換えられます。コード内に this
が現れた場合には、要素への参照を表します。
<table id="my_table" onclick="console.log(this.id);"><!-- `this` refers to the table; logs 'my_table' -->
...
</table>
this
の値は、属性値の中のコードによって呼び出される関数内では、標準的な規則に従って振る舞うことに注意してください。これは次の例で示されています。
<script>
function logID() { console.log(this.id); }
</script>
<table id="my_table" onclick="logID();"><!-- when called, `this` will refer to the global object -->
...
</table>
this
は logID()
内においては、グローバルオブジェクト Window
(または厳格モードの場合は undefined
になります。
bind() を使用した this の指定
Function.prototype.bind()
メソッドで、その関数のすべての呼び出しにおいて this
として使用される値を指定できます。これを使えば、関数がどこから呼び出されるかによって this
の値が変わってしまうというややこしい問題を簡単に回避できます。ただし、リスナーを後で削除できるように、そのリスナーへの参照を残しておく必要があります。
以下は bind()
を使った場合と使わない場合の例です。
const Something = function(element) {
// |this| is a newly created object
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // this は element なので undefined になります
};
this.onclick2 = function(event) {
console.log(this.name); // this はバインドされた Something オブジェクトなので「Something Good」と出力されます
};
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2.bind(this), false); // これが仕掛けです
}
const s = new Something(document.body);
上の例の問題は、 bind()
の付いたリスナーを削除できないということです。もうひとつの解決策は、あらゆるイベントを捕捉する handleEvent()
という特別な関数を使用することです。
const Something = function(element) {
// |this| is a newly created object
this.name = 'Something Good';
this.handleEvent = function(event) {
console.log(this.name); // this は Something オブジェクトなので「Something Good」と出力されます
switch(event.type) {
case 'click':
// 処理
break;
case 'dblclick':
// 処理
break;
}
};
// この場合のリスナーは this であって this.handleEvent でないことに注意してください
element.addEventListener('click', this, false);
element.addEventListener('dblclick', this, false);
// リスナーは適切に削除できます
element.removeEventListener('click', this, false);
element.removeEventListener('dblclick', this, false);
}
const s = new Something(document.body);
this の参照を扱うためのもう一つの方法は、 EventListener
にアクセスする必要のあるフィールドを含むオブジェクトのメソッドを呼び出す関数を渡すことです。
class SomeClass {
constructor() {
this.name = 'Something Good';
}
register() {
const that = this;
window.addEventListener('keydown', function(e) { that.someMethod(e); });
}
someMethod(e) {
console.log(this.name);
switch(e.keyCode) {
case 5:
// some code here...
break;
case 6:
// some code here...
break;
}
}
}
const myObject = new SomeClass();
myObject.register();
イベントリスナーのデータの出し入れ
イベントリスナーは島のようなもので、それらにデータを渡すのは非常に難しく、ましてや実行後にそれらからデータを取り出すのは非常に難しいと思われるかもしれません。イベントリスナーは、イベントオブジェクトという1つの引数を取るだけで、それは自動的にリスナーに渡され、返値が無視されるからです。では、どのようにしてデータを渡したり、取り出したりすることができるのでしょうか?これを行うための良い方法がいくつかあります。
"this" を使用したイベントリスナーへのデータの入力
前述の通り、 Function.prototype.bind()
を使用すると this
参照変数を通じてイベントリスナーに値を渡すことができます。
const myButton = document.getElementById('my-button-id');
const someString = 'Data';
myButton.addEventListener('click', function () {
console.log(this); // Expected Value: 'Data'
}.bind(someString));
この方法は、イベントリスナーの中からプログラムでイベントリスナーがどの HTML 要素で発生したかを知る必要がない場合に適しています。これを行う主な利点は、実際に引数リストにデータを渡す場合とほぼ同じ方法でイベントリスナーがデータを受け取ることです。
外部スコープのプロパティを使用したイベントリスナーへのデータの入力
外部スコープに (const
, let
を付けた) 変数宣言が含まれている場合、そのスコープで宣言されたすべての内部関数はその変数にアクセスすることができます(外部関数/内部関数についてはこちらを、変数スコープについてはこちらを参照してください)。したがって、イベントリスナーの外部からデータにアクセスする最も簡単な方法の1つは、イベントリスナーが宣言されているスコープにアクセスできるようにすることです。
const myButton = document.getElementById('my-button-id');
const someString = 'Data';
myButton.addEventListener('click', function() {
console.log(someString); // Expected Value: 'Data'
someString = 'Data Again';
});
console.log(someString); // Expected Value: 'Data' (will never output 'Data Again')
注: 内側のスコープは外側のスコープにある const
, let
変数にアクセスすることができますが、イベントリスナーの定義後に、同じ外側のスコープ内でこれらの変数にアクセスできるようになることは期待できません。なぜでしょうか?単純に、イベントリスナーが実行される頃には、イベントリスナーが定義されたスコープは既に実行を終了しているからです。
オブジェクトを用いたイベントリスナーのデータの出し入れ
JavaScript のほとんどの関数とは異なり、オブジェクトはそのオブジェクトを参照する変数がメモリ内に存在する限り、メモリ内に保持されます。それに加えて、オブジェクトはプロパティを持つことができ、参照によって渡すことができることから、スコープ間でデータを共有するための有力な候補となります。これについて調べてみましょう。
注: JavaScript の関数は厳密にはオブジェクトです。 (そのため、プロパティを持つことができ、メモリ内に永続的に存在する変数に代入されていれば、実行終了後もメモリ内に保持されます。)
オブジェクトを参照する変数がメモリに存在する限り、オブジェクトのプロパティを使用してメモリにデータを格納することができるので、実際にそれらを使用して、イベントリスナーにデータを渡し、イベントハンドラーが実行された後でデータに変更があった場合には、それを戻すことができます。この例を考えてみましょう。
const myButton = document.getElementById('my-button-id');
const someObject = {aProperty: 'Data'};
myButton.addEventListener('click', function() {
console.log(someObject.aProperty); // Expected Value: 'Data'
someObject.aProperty = 'Data Again'; // Change the value
});
window.setInterval(function() {
if (someObject.aProperty === 'Data Again') {
console.log('Data Again: True');
someObject.aProperty = 'Data'; // Reset value to wait for next event execution
}
}, 5000);
この例では、イベントリスナーとインターバル関数の両方が定義されているスコープは、 someObject.aProperty
の元の値が変更される前に実行を終了していたとしても、イベントリスナーとインターバル関数の両方で someObject
がメモリ内に (参照によって) 持続するため、両方とも同じデータにアクセスできます (つまり、一方がデータを変更したときに、もう一方がその変更に対応できます)。
注: オブジェクトは参照にで変数に格納されます。つまり、実際のデータのメモリの場所だけが変数に格納されます。とりわけ、これはオブジェクトを「格納」する変数が、実際に同じオブジェクト参照が代入 (「格納」) されている他の変数に影響を与えることができるということです。2つの変数が同じオブジェクトを参照している場合 (例えば、 let a = b = {aProperty: 'Yeah'};
)、どちらかから変数のデータを変更すると、もう一方の変数に影響を与えます。
注: オブジェクトは参照によって変数に格納されているので、関数の実行を停止した後も、関数からオブジェクトを返す (データを失わないようにメモリに保存しておく) ことができます。
古い Internet Explorer と attachEvent
IE9 より前の Internet Explorer では、標準の addEventListener
ではなく、 attachEvent()
を使わなければなりません。 IE に対応するためには、上記の例を以下のように修正しなけれなりません。
if (el.addEventListener) {
el.addEventListener('click', modifyText, false);
} else if (el.attachEvent) {
el.attachEvent('onclick', modifyText);
}
attachEvent()
の欠点が 1 つあります。 this
の値がイベントを起こした要素ではなく、 window
オブジェクトへの参照になってしまうことです。
attachEvent()
メソッドは、ウェブページの特定の要素がサイズ変更されたことを検出するために onresize
イベントを対応付けることができました。独自の mselementresize
イベントは、イベントハンドラーを登録する addEventListener
メソッドによって対応付けられた場合。 onresize
と同様の機能を提供し、特定の HTML 要素の寸法が変更されたときに発行されます。
ポリフィル
次のコードをスクリプトの初めに書いておくと、 Internet Explorer 8 では対応していない addEventListener()
, removeEventListener()
, Event.preventDefault()
, Event.stopPropagation()
が動作するようになります。このコードは、 handleEvent()
と DOMContentLoaded
イベントにも対応します。
注: useCapture
に対応していないため、 IE 8 では代わりの方法はありません。以下のコードは IE 8 への対応を追加するだけです。また、 IE 8 用の代替モジュールは、標準モードのみで動作します。 doctype 宣言が必要です。
(function() {
if (!Event.prototype.preventDefault) {
Event.prototype.preventDefault=function() {
this.returnValue=false;
};
}
if (!Event.prototype.stopPropagation) {
Event.prototype.stopPropagation=function() {
this.cancelBubble=true;
};
}
if (!Element.prototype.addEventListener) {
var eventListeners=[];
var addEventListener=function(type,listener /*, useCapture (will be ignored) */) {
var self=this;
var wrapper=function(e) {
e.target=e.srcElement;
e.currentTarget=self;
if (typeof listener.handleEvent != 'undefined') {
listener.handleEvent(e);
} else {
listener.call(self,e);
}
};
if (type=="DOMContentLoaded") {
var wrapper2=function(e) {
if (document.readyState=="complete") {
wrapper(e);
}
};
document.attachEvent("onreadystatechange",wrapper2);
eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper2});
if (document.readyState=="complete") {
var e=new Event();
e.srcElement=window;
wrapper2(e);
}
} else {
this.attachEvent("on"+type,wrapper);
eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper});
}
};
var removeEventListener=function(type,listener /*, useCapture (will be ignored) */) {
var counter=0;
while (counter<eventListeners.length) {
var eventListener=eventListeners[counter];
if (eventListener.object==this && eventListener.type==type && eventListener.listener==listener) {
if (type=="DOMContentLoaded") {
this.detachEvent("onreadystatechange",eventListener.wrapper);
} else {
this.detachEvent("on"+type,eventListener.wrapper);
}
eventListeners.splice(counter, 1);
break;
}
++counter;
}
};
Element.prototype.addEventListener=addEventListener;
Element.prototype.removeEventListener=removeEventListener;
if (HTMLDocument) {
HTMLDocument.prototype.addEventListener=addEventListener;
HTMLDocument.prototype.removeEventListener=removeEventListener;
}
if (Window) {
Window.prototype.addEventListener=addEventListener;
Window.prototype.removeEventListener=removeEventListener;
}
}
})();
イベントリスナーを登録するための古い方法
addEventListener()
は、DOM 2 Events 仕様で導入されました。それ以前は、以下のようにイベントリスナーを登録していました。
// 関数へのリファレンスを利用する方法—'()' が無いことに注意してください
el.onclick = modifyText;
// 関数式を利用する方法
element.onclick = function() {
/* ...文... */
};
このメソッドは、要素上に click
イベントリスナーが既に存在する場合、置き換えてしまいます。 他のイベント、blur
(onblur
)、keypress
(onkeypress
)、などのような関連するイベントハンドラも同様です。
これは本質的に DOM 0 の一部であるため、イベントリスナーを追加するためのこのテクニックは非常に広く対応されており、特別なブラウザー間の互換コードを必要としません。これは、 (IE 8 以前のような) 非常に古いブラウザーに対応しなければならない場合に、イベントリスナーを動的に登録するために使用されます。 addEventListener
のブラウザー対応の詳細については、下記の表を参照してください。
メモリに関する問題
const els = document.getElementsByTagName('*');
// ケース 1
for(let i=0 ; i < els.length; i++){
els[i].addEventListener("click", function(e){/*関数の処理*/}, false);
}
// ケース 2
function processEvent(e){
/*関数の処理*/
}
for(let i=0 ; i < els.length; i++){
els[i].addEventListener("click", processEvent, false);
}
上記の最初のケースでは、ループが繰り返されるたびに新しい (無名の) ハンドラー関数が作成されます。 2 番目のケースでは、以前に宣言された同じ関数がイベントハンドラーとして使用され、作成されるハンドラー関数が 1 つしかないため、メモリ消費量が少なくなります。さらに、最初のケースでは、無名関数への参照が保持されないため (ここでは、ループが作成する可能性のある複数の無名関数のいずれも保持されないため)、 removeEventListener()
を呼び出すことができません。2つ目のケースでは、 processEvent
が関数への参照なので、 myElement.removeEventListener("click", processEvent, false)
を実行することが可能です。
実際には、メモリ消費に関しては、関数への参照が保持されていないことが本当の問題ではなく、静的な関数への参照が保持されていないことが問題なのです。以下の問題ケースでは、どちらも関数への参照は保持されていますが、繰り返しのたびに再定義されているため、静的なものではありません。 3 つ目のケースでは、無名関数の参照が反復のたびに再割り当てされています。 4 番目のケースでは、関数の定義全体は不変ですが、 (コンパイラによって[[昇格]]されていない限り) 新しいものとして繰り返し定義されているので、静的ではありません。したがって、単純に[[複数の同一のイベントリスナー]]のように見えますが、どちらの場合も、各反復処理では、ハンドラー関数への独自の参照を持つ新しいリスナーが作成されます。しかし、関数の定義自体は変更されないので、重複するリスナーごとに同じ関数が呼び出される可能性があります (特にコードが最適化されている場合)。
また、どちらの場合も、関数への参照は保持されていたが、追加するごとに繰り返し再定義されていたので、上からの remove 文でリスナーを削除することができますが、最後に追加されたものだけが削除されるようになります。
// For illustration only: Note "MISTAKE" of [j] for [i] thus causing desired events to all attach to SAME element
// ケース 3
for(let i=0, j=0 ; i<els.length ; i++){
/* do lots of stuff with j */
els[j].addEventListener("click", processEvent = function(e){/*do something*/}, false);
}
// ケース 4
for(let i=0, j=0 ; i<els.length ; i++){
/* do lots of stuff with j */
function processEvent(e){/*do something*/};
els[j].addEventListener("click", processEvent, false);
}
パッシブリスナーを用いたスクロールの性能改善
仕様書によれば、 passive
オプションの既定値は常に false
です。しかし、これは特定のタッチイベントを扱うイベントリスナーが (特に) スクロールを処理しようとしている間にブラウザーのメインスレッドをブロックする可能性をもたらしており、スクロール処理中の性能が大幅に低下する結果になる可能性があります。
この問題を防ぐために、一部のブラウザー (特に Chrome と Firefox) では、文書レベルノードである Window
, Document
, Document.body
の touchstart (en-US)
および touchmove (en-US)
イベントの passive
オプションの既定値を true
に変更しています。これにより、イベントリスナーが呼び出されなくなるため、ユーザーがスクロールしている間にページのレンダリングをブロックすることができなくなります。
Note: この変更された動作を実装しているブラウザー (およびそれらのブラウザーのバージョン) を知りたい場合は、下記の互換性一覧表を参照してください。
この動作は下記のように、明示的に passive
の値を false
に設定することで上書きできます。
/* Feature detection */
let passiveIfSupported = false;
try {
window.addEventListener("test", null,
Object.defineProperty(
{},
"passive",
{
get: function() { passiveIfSupported = { passive: false }; }
}
)
);
} catch(err) {}
window.addEventListener('scroll', function(event) {
/* do something */
// can't use event.preventDefault();
}, passiveIfSupported );
addEventListener()
の options
引数に対応していない古いブラウザーでは、これを使用しようとすると、機能検出を適切に使用せずに useCapture
引数の使用を防ぐことがあります。
基本的な scroll (en-US)
イベントの passive
の値を気にする必要はありません。キャンセルできないので、イベントリスナーはどのような場合でもページのレンダリングをブロックすることはできません。
仕様書
仕様書 | 状態 | 備考 |
---|---|---|
DOM EventTarget.addEventListener() の定義 |
現行の標準 | |
DOM4 EventTarget.addEventListener() の定義 |
廃止 | |
Document Object Model (DOM) Level 2 Events Specification EventTarget.addEventListener() の定義 |
廃止 | 初回定義 |
ブラウザーの互換性
BCD tables only load in the browser