IMG_5256
Development

Vanilla JavaScriptを書いてみよう その1

ブラウザ内でアニメーションをさせたり、ユーザーの操作に反応して何かを行う場合必要になるのがJavaScriptです。

最近でこそJavaScriptは便利なライブラリやプリプロセッサが充実してるので、JavaScriptを使ったプログラミングは簡単に始められて楽しいものですが、一昔前まではブラウザ間の差異に悩まされめんどくさいという印象が強かったのでは無いかと思います。

そこで登場したのがjQueryでした。便利なセレクターやメソッドチェーンで簡潔にわかりやすく、そしてクロスブラウザで動作するスクリプトを書けるとあって、これ以外に選択肢を考えることすら無くなったのではないかと思います。

ところで昔からweb制作ではIE対応に悩まされてきた方が多いと思いますが、時代は流れ悪名高いIE6や7の対応はほぼ無くなって2014年現在ではIE8以上としている場合が殆どではないでしょうか。IE8ですら最近は切り捨てられることも増えてきているようです。

さらに最近のブラウザのJavaScriptは進化していて、今までjQueryの領域だと認識されていた部分にも素のJavaScriptが追い付いてきているようです。タイトルにも書きましたが、Vanilla JavaScriptというのは新しいライブラリやフレームワーク等ではなく、素で真っ白な状態のJavaScriptという意味です。

Vanilla JavaScriptだと何がいいのか?jQuery等の読み込みが無くなるのでhttpリクエストをひとつ減らせます。ただ最近ではサイトの公開時にJavaScriptをファイル1つにまとめてを圧縮しておくことも多くなったのでそこまで大きなメリットのようには思えません。ただスマートフォンやタブレットが普及しサイトの半数近くがそれらのモバイルデバイスであり、PCにおいてもモダンブラウザが多数を占めているなら簡単なものはVanilla JavaScriptで書くことで読み込みや実行速度を高めることに一役買うでしょう。

またjQueryの内部ではなにが行われているのか?全ての処理がjQueryの独自実装で動いているのか?そんなことはありません。実はブラウザに同様の機能がある場合はそちらを使うように設計されています。ですのでアニメーションなどは行わずにシンプルなDOM操作のみの場合はjQueryの旨味を十分に発揮させていないことがあるのです。

今回はそんなVanilla JavaScriptではコードをどのように記述していくのかを紹介したいと思います。

イベントの登録を行ってみよう

まずはなにはなくともブラウザ上で動くJavaScriptといえばイベントハンドリングが出来ないと話になりません。なぜならhtmlが読み込まれDOMが構築された後でないと満足にページ内の操作が行えないからです。

body要素にonload属性を追加して関数を実行したり、windowオブジェクトのonloadプロパティに関数を代入する方法でも確実にDOM構築後にスクリプトを実行することが可能ですが、せっかくならjQueryで普通にやっていることを置き換える形で試してみたいと思います。

<script type="text/javascript">
   function start() {
     ...
   }
 </script>
 <body onload="start()">
   ...
 </body>

これでも動くし

<script type="text/javascript">
   window.onload = function() {
     ...
   }
 </script>

これでも動くけど…

<script type="text/javascript">
   jQuery(function($) {
     ...
   });
 </script>

せっかくならこれに置き換わる形で書こう!

JavaScriptでイベント登録を行う関数といえばaddEventListenerがありますが、残念ながらこれはIE8では動作しません。ではどうするか?IE8以前にはattachEventという関数があります。これをif文で使い分けるようにするaddEvent関数を作って対応しましょう。

function addEvent(obj, event_name, handler) {
   if (obj.addEventListener) {
     // addEventListenerが使える場合
     obj.addEventListener(event_name, handler, false);
   } else if (obj.attachEvent) {
     // attachEventが使える場合
     obj.attachEvent('on' + event_name, handler);
   }
 }

次にこれを使ってDOM構築が完了したイベントにハンドラを登録するには以下のようにします。

addEvent(window, 'load', function() {
   alert('Hello');
 });

上記のように書けばwindowのloadイベントに対して何度イベントハンドラを登録したとしても先の処理を上書きすること無く全てが実行されます。

addEvent(window, 'load', function() {
   alert('Hello');
 });
 addEvent(window, 'load', function() {
  alert('World');
 });

HelloにつづいてWorldもアラートされるはずです。

jQueryのようにHTML要素を取得できるquerySelector、querySelectorAllを使ってみよう

イベントハンドリングの壁は超えられたので次にhtml要素を取得してみましょう。

jQueryの大きな特徴としてcssのように使える便利なセレクターがあります。これはページ内にあるDOMノードをJavaScriptから見つけるために欠かせない手順ですが、従来のJavaScriptではあまり開発者にやさしくないものでした。

しかしモダンなブラウザはもちろんIE8以降で使える関数でquerySelectorとquerySelectorAllという2つがあります。前者はセレクタに該当する最初の要素を取得し、後者はセレクタに該当する全ての要素を取得して配列で返してくれます。

<h1>Hello</h1>

このような要素があったとして

addEvent(window, 'load', function() {
  var h1 = document.querySelector('h1');
  alert(h1.innerText);
 });

というスクリプトを書くとh1要素の中身のテキストがアラートされます。 ちなみにquerySelectorを使った場合はh1要素が複数あった場合最初の要素のみ取得されるので

<h1>Hello</h1>
<h1>World</h1>

この場合もHelloのみがアラートされます。

querySelectorAllを使った場合は要素は配列に格納されて返されますので

addEvent(window, 'load', function() {
  var h1s = document.querySelectorAll('h1');
  for (var i in h1s) {
    alert(h1s[i].innerText);
  }
});

と書くとHelloにつづいてWorldもアラートされます。

さらにこれらの関数の便利な点はDOMツリーのスコープが適用されるというところです。上記の例ではdocumentオブジェクトというwindowオブジェクトに続く2番めに大きいオブジェクトですので要素の検索はページ全体に渡ります。しかし2つ目のdiv要素の中にあるh2のテキストのみをアラートしたい場合はどうすればよいでしょうか。しかもh2にはclassもidも降ってないとしたら検索は更に困難になります。

そのような場合にquerySelectorのスコープを活用すると便利です。

<h2>zero</h2>
<div>
  <h2>first</h2>
</div>
<div>
  <h2>second</h2>
</div>

このような要素があるとして

addEvent(window, 'load', function() {
  var h2 = document.querySelectorAll('div')[1].querySelector('h2');
  alert(h2.innerText);
});

このようにすればsecondとアラートされます。
まず最初にdocumentの持っているスコープから2番目のdiv要素を見つけ、そのdiv要素の中にあるh2を見つけたという流れです。querySelectorAllで見つかった要素は配列に格納されているので2番目の要素は[1]で取り出せるというわけですね。

まとめ

いかがでしたでしょうか?このエントリーで書いたJavaScriptはIE8でも問題なく動きます。ちょっとした処理ならjQuery無しで書いてみようかな?という気が少しでも持てれば幸いです。
若干jQuery使用時に比べると簡潔ではありませんが、それでも工夫次第(例えばメソッドチェーンを多用しすぎないとか、別の処理は行を分ける等)で綺麗に書くことも可能だと思います。なによりライブラリに頼らないことでよりJavaScriptに対する理解が深まるのはいいことですよね。

次回では取得した要素に対して変更を行い、実践的な内容に挑戦してみたいと思います。

標準