これでできる! クロスブラウザJavaScript入門

第7回 JavaScriptとHTMLとDOMの基本#2 イベント編

この記事を読むのに必要な時間:およそ 6 分

こんにちは,太田です。前回はJavaScriptからみたHTMLの基本を中心に解説しました。今回はまず,イベントについて解説します。JavaScript,DOMにおいてイベントは極めて重要です。ブラウザ上のJavaScriptでは必ずといってよいほどイベントが絡んでいますし,ウェブアプリケーションをコントロールする根幹的な技術と言えるほどです。

JavaScriptとイベント



ブラウザはscriptタグで指定されたJavaScriptを解釈して実行します。その時,関数などを定義するだけにして,実際にその処理が行われるのはユーザーがボタンをクリックした時や,何かを入力した時など,ユーザーの何らかのアクションに関連付けてJavaScriptを実行させることができます。さらには,ユーザーのアクションだけでなく,ページの読み込みや通信処理の完了後など,ブラウザ上で起こるあらゆるイベントについて処理を行うのがJavaScriptの特徴です。このようにイベントを中心としたプログラミングをイベントドリブン(イベント駆動型)プログラミングと呼びます。特に,ユーザーのアクションに対応して適切な処理することはJavaScriptの醍醐味ともいえます。

さて,イベントを扱うにあたって,大きく2つの方法があります。1つはHTML4.01などで定義されているシンプルでHTMLと直に結びついた方法(便宜的に以降はonclick方式と呼びます)で,もう1つはDOM Level 2 Eventsで定義された高機能で抽象化された方法(DOM方式)です。onclick方式はシンプルで扱いやすい上にそのままで(ほぼ)クロスブラウザに扱えるため便利ですが,シンプルすぎるゆえの問題もあります。一方,DOM方式は前回も少し書いたように,IEがDOM Level 2相当のAPIを独自仕様で実装しているためクロスブラウザ対応に手間がかかります。使用頻度の高いイベント周りの処理をIEの独自仕様に対応しなければいけない点がクロスブラウザの難易度を大きく引き上げているという面があり,逆に言えばこのイベント周りをしっかり押さえておけばクロスブラウザが一気に簡単になります。

まずはonclick方式を簡単に解説します。onclick方式の最大の特徴はHTML中に書くことができるという点です。

HTMLタグでのonclick方式

<body onload="init();">
<input id="url-alert" type="button" onclick="alert(location.href);" value="URLをアラート">
</body>

見た目のままの通り,onload(ページ全体の読み込みの完了)でinitという関数が呼ばれ,ボタンをクリックするとURLがアラートで表示されます。また,JavaScriptからonclick,onloadを設定することも可能です。

JavaScriptからのonclick方式

window.onload = function(){
  var ua = document.getElementById('url-alert');
  ua.onclick = function(){
    alert(location.host);
  };
};
function init(){
  alert(location.href);
}

このonclick方式の問題点は,ある要素のあるアクションに対して1つの処理しか登録できないという点です。つまり,上記の2つのコードを組み合わせたとき,HTML上に書かれたonloadとJavaScriptで設定されたonloadが重複しています。どちらが有効になるかはJavaScriptが実行されるタイミング,この場合scriptタグを記述する場所に左右されます。script要素がbodyタグの前(head要素の中)に記述した場合はinit関数が呼ばれ,script要素がbodyタグの中に記述されていた場合はwindow.onloadで定義した処理が実行されます。

そのため,HTMLがシンプルであったり,イベント処理が重複しないように配慮できるのであればonclick方式を使っても問題ありませんが,大きなHTMLであったり,複数人で編集するようなケースではこの方法はおすすめできません。

なおonclickは属性なので,setAttributeメソッドを使って定義することも可能ですが,その場合文字列で関数を定義することになるのでおすすめできません。また,IEのsetAttributeメソッドは node[name] = value と書いた場合とまったく同じ動作をするので,少々厄介です。詳しくはIE の getAttribute / setAttribute: Days on the Moonで説明されていますが,クロスブラウザを考えたらsetAttributeは避けたほうがよいと覚えておくとよいでしょう。

DOM Event

では,続いてDOM仕様に沿ったイベント処理(の登録周り)を解説します。⁠既に連載の中では登場していますが)DOM Level 2 Events(とDOM Level 3 Eventsではイベント登録用メソッドとしてaddEventListener(IE独自仕様ではattachEvent)が定義されています。登録した処理を削除するのはremoveEventListener(同じくIEはdetachEvent)です。なお,IEとの互換性のために,OperaなどもattachEventを実装しています。そのため,両方のメソッドを使うと2回呼ばれてしまうので注意が必要です。

※公開当初,Safari,ChromeもattachEventをサポートしていると記述していましたが,これは誤りでした。お詫びして訂正いたします。

addEventListenerはElementのメソッドとして定義(Element.prototype.addEventListener)されており,すべてのHTML要素はElementを継承しています。このようにDOM仕様には継承という概念がはっきりと存在しています。JavaもDOMをサポートしているように,DOM周りは比較的仕様が厳密に定義されています。

addEventListenerの第3引数

addEventListenerには3つ引数がありますが,attachEventは2つしかありません。そのため,この3つ目の引数はクロスブラウザにおいてはfalseにして置くとだけ覚えておけばとりあえずは十分です。

ただ,折角なのでもう少し詳しいところも解説します。まず,HTMLはhtmlタグを最上位として,その中にheadタグ,bodyタグを持ち,さらにその中に……,というように階層的な構造をしています。より正確にはツリー構造と言われています。またツリー構造をした要素の集合を文書木と表現することもあります。あるウェブページで適当な要素をピックアップしてみると,その要素は必ずそのページを構成する文書木に属しており,すなわち親要素,祖先要素を持っています。

さて,あるボタンをクリックした時,そのボタンが含まれるdiv要素だったり,form要素だったり,さらにはbody要素,html要素まで,ボタンを含む(階層的に上にある)要素もクリックされた要素として認識されます。これはHTMLが階層構造をしているため必然的にそうなります。そのボタンがhtml, body, form, divなどの各要素という「箱」でラッピングされており,そのボタンに触れるためにはすべての「箱」を開封することをイメージしてください。ここで,イベントには「箱」を開いていくプロセスと,箱を閉じていくプロセスがあります。箱を開いていくプロセスをキャプチャリングフェーズ,箱を閉じていくプロセスをバブリングフェーズと呼びます。

図1 HTMLの階層構造とイベントの伝播

図1 HTMLの階層構造とイベントの伝播

このどちらのプロセスを見るのかを決定するのがaddEventListenerの第3引数です。仮に,イベントの伝搬を止めたい場合,上位のDOMノードのキャプチャリングフェーズでイベントを止めれば,より早くイベントを止めることが可能となります。ただし,前述の通りIEのattachEventはバブリングフェーズでしかイベントを扱えないため,addEventListenerの第3引数を使うケースは多くありません。

さて,addEventListenerといえば既にaddEvent関数をなんども取り上げていますね。改めて,addEvent関数を見てみましょう。

イベント登録関数

var addEvent = (function(){
  if(document.addEventListener) {
    return function(node,type,handler){
      node.addEventListener(type,handler,false);
    };
  } else if (document.attachEvent) {
    return function(node,type,handler){
      node.attachEvent('on' + type, function(evt){
        handler.call(node, evt);
      });
    };
  }
})();

このaddEvent関数はイベントはもちろん,高階関数にクロージャも絡んでおり,さらに実装による振り分け,仕様の差を埋めるための処理を入れたりなど,クロスブラウザのエッセンスのほとんどが含まれています。是非,この関数をしっかりと噛み砕いて理解してみてください。理解できたかの確認には誰かに説明してみることを想定してみるとよいかもしれません。

著者プロフィール

太田昌吾(おおたしょうご,ハンドルネーム:os0x)

1983年生まれ。JavaScriptをメインに,HTML/CSSにFlashなどのクライアントサイドを得意とするウェブエンジニア。2009年12月より、Google Chrome ExtensionsのAPI Expertとして活動を開始。

URLhttp://d.hatena.ne.jp/os0x/

コメント

コメントの記入