Help us understand the problem. What is going on with this article?

jQuery.on()は追加式なのでイベントの重複登録に注意しよう

More than 3 years have passed since last update.

はじめに

jQuery.on()は動的に追加したDOMに対しても簡単にイベントを追加できる便利なメソッドです。一方でaddEventListenerとは異なり、全く同じイベントでも重複登録できてしまうため、注意が必要となるケースがあります。

基本的な知識かもしれませんが、見落としてしまったので備忘録として残しておきたいと思います。これ以上被害者が増えませんように......

jQuery.click()とかでも同じです

目次

  • はじめに
  • どういった時に何が起きるか
  • 重複を回避するには
    • addEventListenerを使う
    • on()と同時にoff()を使う
  • おまけ
  • 終わりに

どういった時に何が起きるか

ページ読み込み時に一回イベントを設定するだけであれば、問題は起こりません。
しかし、以下のようなイベント登録が複数回処理されうる場合は注意が必要です。

ボタンが押されたらDOMにクリックイベントを設定する.js
var fnc = function() {
  console.log('押された!');
};

$('button').on('click', function() {
  $('#dom').on('click', fnc);
});

[動作イメージ]
anim.gif

一見問題なく動いているように見えます。
しかし連続してクリックすると......

anim2.gif

なんということでしょう......
クリックした分だけ関数が重複処理されてしまいました。

このサンプルコードは単純なためまだ気づきやすいですが、実際に複雑な処理が絡まってくると見落とすかもしれません。

重複を回避するには

そもそもイベント設定は一回だけの設計にすれば良いのですが、それができない・やりたくない場合もあるかと思います。そんな時には以下のような回避策を取ることができます。

addEventListenerを使う

jQueryメソッドはやめて、普通のEventListenerを使います。それだけで重複登録を防ぐことができます。

代わりにaddEventListenerを使う.js
$('button').on('click', function() {
  $('#dom')[0].addEventListener('click', fnc);
});

[引用]
複数の同一の EventListener が、同じ EventTarget に同じ引数で登録された場合、重複するインスタンスは反映されません。EventListenerが2度呼び出されることはなく、重複するインスタンスは反映されない

https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener

ただしDOM自体が動的生成される場合など、これだけでは対応しきれないケースもあります。

on()と同時にoff()を使う

.on()実行の前に一旦.off()でイベントを削除することで重複登録を防ぐことができます。
.on()はまだ存在しないDOMへのイベント設定も可能なため、前述のaddEventListenerだけでは対応しきれないケースにも使えます。

off()を使う.js
$('button').on('click', function() {
  $('#dom').off('click');
  $('#dom').on('click', fnc);
});

.off()を使う時の注意点

.off()は指定されたセレクタに対し、jQueryで登録したイベントを全て削除するため、更に別でイベント登録していると巻き込んでしまいます。

off()は他で登録したイベントも巻き込んで削除してしまう.js
$('#dom').on('click', function() {
  console.log('hoge');
});

$('button').on('click', function() {
  $('#dom').off('click');  // ここで
  $('#dom').on('click', fnc);
});

[動作イメージ]
anim3.gif

最初に設定していたconsole.log('hoge');が消えてしまっています。
これは名前空間を指定することで回避できます。名前空間は以下のようにして設定することができます。

名前空間を設定する.js
$('#dom').on('click', function() {
  console.log('hoge');
});

$('button').on('click', function() {
  $('#dom').off('click.namae');
  $('#dom').on('click.namae', fnc);
});

[動作イメージ]
anim4.gif

console.log('hoge');の方は消えずに、またイベントも重複処理されていないことが分かります。

おまけ

いつ頃からか、Chrome Devtools上でjQueryで設定したイベントを直接見ることができるようになっていました。これを使えばパッと見ておかしいことに気づきやすくなりますね!
※Framework listenersにチェックが入っている必要があります

[動作イメージ]
anim5.gif

終わりに

jQuery.on()を意識せず使いまくっていると困ることがあるかもしれないよ、という実体験に基づくお話でした。

※他にも既に同じイベントが登録されているかどうかで条件分岐、という手段も考えましたが処理が冗長になるだけかと思い掲載していません。

nekoneko-wanwan
Webデザイン、フロントエンド開発を主にやっています。真面目なものから、変なものまで。色々な記事を投稿していければと思います。
hotstartupinc
「ペライチ」を開発する会社です。
https://peraichi.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした