uhyohyo.net

JavaScript初級者から中級者になろう

三章第二回 イベントリスナ

addEventListener

DOM的にイベントを登録する(イベントが起こったとき関数が動くようにする)には、addEventListenerを使います。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p id="abcd">test</p>

    <script type="text/javascript">
      function aaa(){
          console.log('aaa!');
      }

      var p = document.getElementById('abcd');

      p.addEventListener('click', aaa, false);
    </script>
  </body>
</html>

このサンプルで、p要素をクリックすると、aaa関数が実行されます。これは、もちろんaddEventListenerによるものです。

これはノードが持つメソッドで、見て分かるように3つの引数をとります。

一つ目の引数はイベント名です。今回の場合'click'ですね。「onclick」のようにonはつかないことに注意しましょう。ここで指定したイベントの種類について、イベントが登録されます。つまり、今回の場合、クリックした場合のイベントを登録しています。

二つ目の引数はイベントリスナです。イベントリスナとは、要するに呼び出される関数です。関数のオブジェクト一章第四回)を渡しています。

さて、三つめの引数はフェーズというものの設定です。真偽値であることが分かります。これは、もっと後で説明するので、とりあえずfalseにしておきましょう。これの説明は三章第四回で行います。

まとめると、要するに、この関数で、イベントが発生したときに呼ばれる関数を設定しているのです。今回の場合は「clickが起こったとき(クリックされたとき)に関数aaaを呼ぶ」という設定をしているのでした。

これは、p.onclick = aaa;と同じような動作をしますね。

利点

さて、このメソッドを使うことにどんな利点があるかを紹介したいと思います。一つに、複数登録できるということがあります。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p id="abcd">test</p>

      <script type="text/javascript">
        function aaa(){
            console.log('aaa!');
        }
        function bbb(){
            console.log('bbb!');
        }

        var p = document.getElementById('abcd');

        p.addEventListener('click', aaa, false);
        p.addEventListener('click', bbb, false);
    </script>
  </body>
</html>

addEventListenerで関数aaaを登録したあと、続けて関数bbbも登録しています。

このサンプルでp要素をクリックしてみると、なんとログが2回表示されます。これから、関数aaaとbbbが両方呼ばれたことが分かります。

これは、前回のイベントプロパティを使う方法とは異なる点のひとつです。

p.onclick = aaa;
p.onclick = bbb;

のように2回関数を代入すると上書きされてしまうことは、前回説明しました。

つまり、addEventListenerを使えば上書きされないということです。これなら前回説明した問題点を解消することができます。

他にもありますが、とりあえず、JavaScriptからイベントを登録するときは、このaddEventListenerを使うようにしましょう。

removeEventListener

反対に、登録されているイベントをなくすというメソッドもあります。それがremoveEventListenerです。使い方はaddEventListenerと同じで、まったく同じ引数3つを渡します。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p id="abcd">test</p>

    <script type="text/javascript">
      function aaa(){
          console.log('aaa!');
      }

      var p = document.getElementById('abcd');

      p.addEventListener('click', aaa, false);
      p.removeEventListener('click', aaa, false);
    </script>
  </body>
</html>

上のほうのサンプルにremoveEventListenerの一行を追加したことで、p要素をクリックしてもログが表示されなくなりました。引数はまったく同じです。

ちなみに、2つめの引数は関数名(正確には関数をあらわす変数の名前)ですが、変数名まで同じである必要はありません。おなじ関数を表していればいいのです。つまり、

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p id="abcd">test</p>

    <script type="text/javascript">
      function aaa(){
          console.log('aaa!');
      }

      var p = document.getElementById('abcd');

      p.addEventListener('click', aaa, false);
      var b = aaa;
      p.removeEventListener('click', b, false);
    </script>
  </body>
</html>

のように書いた場合でも正しく動作するということです。

変数bにaaaを代入しています。aaaはある関数を表しているから、bも同じ関数を表すようになります。よって、removeEventListenerにbを渡せば、ただしく処理されるということになります。

また逆に、無名関数を登録したら消すことができません。次のサンプルで確かめましょう。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p id="abcd">test</p>

    <script type="text/javascript">
      var p = document.getElementById('abcd');

      p.addEventListener('click', function(){
        console.log('aaa!');
      }, false);
      p.removeEventListener('click', function(){
        console.log('aaa!');
      }, false);
    </script>
  </body>
</html>

この書き方はわかりにくいかもしれません。addEventListenerの呼び出し部分は、引数は次の3つです。すなわち、'click'

function(){
    console.log('aaa!');
}

falseです。このように、別に関数呼び出しが複数行にまたがってもいいわけですね。ここでは2つ目の引数が無名関数式になっていて、3行使っています。removeEventListenerの引数も全く同じですね。ちなみに無名関数については一章第四回で説明しました。

ところが、このように書いた場合、p要素をクリックするとログが表示されます。なぜかというと、function(){ 〜 }という式はその場で新しい無名関数を作るので、addEventListenerに渡された無名関数とremoveEventListenerに渡された無名関数は処理が全く同じであっても別物扱いだということになります。ですから、removeEventListenerに渡された関数は、addEventListenerに渡された関数とは別物であると考えられるので、消せなかったのです。このように、いちど無名関数を登録してしまったら消せません。あとで消したいものには名前をつける(変数に入れて参照できるようにする)必要があります。

おまけ:IEへの対応

実は、addEventListenerやremoveEventListenerに、IEは今のところ対応していません(追記:IE9で対応しました)。しかし、IEは独自のメソッドなどを作り、似たようなことができるようにしています。

これはDOMに従っていないから放っておいてもいいし、現に基礎第一回で「IEなんてやめましょう」というようにしたのですが、現実的にはまだまだIEでWebサイトに訪問する人も多く、それを全て切り捨てるのも(JavaScriptが動作しなくても最低限見れるページを作るのが望ましいのですが)、あまりいい話ではないかもしれません。そこで、IEで動くようにする方法を一応紹介しておきます。

IEでは、上のサンプルと同じことをしたい場合、次のようにします。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p id="abcd">test</p>

    <script type="text/javascript">
      function aaa(){
          console.log('aaa!');
      }

      var p = document.getElementById('abcd');

      p.attachEvent('onclick', aaa);
    </script>
  </body>
</html>

addEventListenerの代わりに、attachEventというメソッドが使われています。これが、IEでイベントリスナを登録するメソッドです。1つめの引数はイベントの種類で、2つめはイベントリスナです。これはaddEventListenerと同じですね。3つめの引数がありませんが、これはfalseを指定したのと同等です。

ただ、第一引数に違いがあります。addEventListenerではイベント名に「on」を付けないのに、attachEventではつけていますね。

反対に、detachEventというメソッドもあります。これは、removeEventListenerにあたるもので、引数はattachEventと同じです。

さて、IEの人が困るからといってattachEventを使うようにすると、今度はIE以外の人が困ります。そこで、共存させるためにはこのようにします。

if(p.addEventListener){
  p.addEventListener('click', aaa, false);
}else if(p.attachEvent){
  p.attachEvent('onclick', aaa);
}

ifの条件にp.addEventListenerを指定しています。もしaddEventListenerがあれば、関数オブジェクトが入っているため真で、addEventListenerが呼ばれます。

対して、p.addEventListenerがないIEでは、まったく想定外のプロパティが参照されたことになるので、undefinedとなり、つまり偽です。このあたりのことについては、二章十四回で解説しましたね。

同じことを一応attachEventでもしています。