動的に追加した要素にon()でイベントを設定する仕組みの簡単な説明

More than 1 year has passed since last update.

普通にイベントを設定

<body>
    <input id="elem1" type="text" />
</body>

というHTMLで、

$('input').on('click', function);

とした場合、id="elem1"のinputに対してclickイベントが設定されます。

動的要素へのイベントの設定

同じHTMLで、inputへのclickイベントの設定と、動的なinputの要素追加を行うとします。

$('input').on('click', function); //1

$('body').append('<input id="elem2" type="text" />'); //2

単純に$('input')に対して.on()でイベントを設定した場合、
あとから追加したid="elem2"ではイベントが発生しません。

順を追って見ると

上記のコードは、

  1. $('input').on('click', function);でinputにイベントを設定し、id="elem1"にclickイベントが設定される
  2. id="elem2"のinput要素を追加

という順序で実行されます。

2.の時点でHTMLは

<body>
    <input id="elem1" type="text" />
    <input id="elem2" type="text" />
</body>

となりますが、
1.の時点ではid="elem2"のinputは存在しないため、イベントは実行されません。

解決方法

こういうときには、以下のように書くと動的に追加した要素でもイベントが実行されます。

// $('input').on('click', function); //1
$('body').on('click', 'input', function); //1'

$('body').append('<input id="elem2" type="text" />'); //2

以下が本題

上記の解決方法に記載した書き方は、
clickイベント自体はinputではなくbodyに設定されています。

ですが、on()の第二引数にセレクタを設定しているため、
body以下でinputをクリックした場合に実行されるイベント
となり、動的に追加したinputに対してもclickイベントが実行されるようになります。

どういうことか簡単に説明すると

それぞれのon()の引数による違いは、

$(selector).on(event, function)
  1. selectorのeventが発生した時に
  2. functionを実行する
$(selector).on(event, target, function)
  1. selectorのeventが発生した時に
  2. eventの発生元がtargetであれば
  3. functionを実行する

ということになります。

eventの発生元

JavaScriptでは、子要素で発生したイベントは親要素まで順に遡って発生します。

参考: jQueryのバブリングと、「return false;」「e.stopPropagation();」「e.preventDefault();」について

以下の書き方であれば、

$('body').on('click', 'input', function)

bodyにclickイベントが設定されているのでイベントが発生し、
このclickは、inputをclickしたことから遡ってきたものなのでfunctionが実行される、
ということになります。

ということは

$('body').on('click', 'input', function)

とした場合、
body自体をクリックしたり、input以外の要素をクリックした場合でもイベントは発生しています
ただ、発生元がinputではないため、functionは実行されていないだけです。

大抵はイベントを設定したい要素はある一定の範囲にしか無いと思うので、
単純に$('body').on()とするよりは、
できるだけ小さい範囲に設定した方がパフォーマンスの観点からも望ましいです。

例えば

<body>
    <header>~~~</header>
    <section>
        <div id="input-form">
            <input type="text" />
        </div>
    <section>~~~</section>
    <footer>~~~</footer>
</body>

のような構成であるなら、

$('#input-form').on('click', 'input', function);

というような形にしたほうが良いでしょう。

余談

確か

jQueryのイベント設定系のメソッドがon()に集約していったのも、

.live()とか.delegate()とか.bind()いろいろあるけどわかりづらいから.on()だけにしようよ

みたいな流れがあったのも理由だったような。

$('input').live('click', function)

としていても、内部では

$('body').bind('click', 'input', function)

ってやってるのと同じで、

inputに設定してるような書き方なのにinputに設定されないのはおかしくね?

みたいな感じだったなーと、記憶の片隅に残っています。ソースは忘れた。