Tweet about this on TwitterShare on FacebookShare on Google+Share on LinkedInEmail this to someone

最近は大規模なWebサイトの開発を行っているのですが、わりとリッチな画面が多く、結構な量のJavaScriptを書きました。
小規模のWebサイトであれば、あまり意識する必要はありませんが、大規模になると出来る限り汎用的な設計・実装をしないと後々の仕様変更や機能の拡張で苦労する事になります。
たとえば、汎用的でない実装をしたために同じような機能を幾つも修正しないといけないという事が起こり、対応漏れやバグの温床となり得ます。

そのため、大規模なWebサイトではあらゆる場面で再利用できるように設計を考える必要があります。
フレームワークなどを使う方法もありますが、今回は汎用的な処理を実装するために僕自身が意識している事を書きたいと思います。
なお、あくまで考え方を書いているので記事内のコード自体は簡易的なものにしています。そのため、そのままのコードを実行しても動作しませんので、ご了承ください。

汎用的に使えるプログラムを書くには?

機能は最小単位で構成する

機能を最小単位で構成して、必要に応じて機能を組み合わせる事で、同じ機能のコードを書かずにあらゆるインターフェースに柔軟に対応する事ができます。
たとえば、以下のような機能があったとします。

カルーセル画像2

この場合は、以下のように機能を分割し、それぞれの機能ごとにclassを割り当てます。
HTMLに機能のclassが指定された場合は単独でも動作するように実装します。

//要素をスライドするカルーセル機能
Carousel = function () {
     //.jsc-carouselが割り当てられた要素の子孫にある画像をカルーセルにする処理を実装
     ~~~ 割愛 ~~~
};
new Carousel( $('.jsc-carousel') );

//画像をタップすると拡大プレビューされる機能
ImagePreview = function ( $element ) {
     //.jsc-imagepreviewが割り当てられた要素の子孫にある画像をタップした際にプレビューする処理を実装
     ~~~ 割愛 ~~~
}
new ImagePreview( $('.jsc-imagepreview') );

最終的には上記の2つの機能を組み合わせる事で前述しているインターフェースを実現します。
そのため、HTML上では上記2つの機能を動作させるためのclassを指定します。

<div class="jsc-carousel jsc-imagepreview">
     <ul>
          <li><img src="sample1.jpg" alt=“画像その1"></li>
          <li><img src="sample2.jpg" alt=“画像その2"></li>
          <li><img src="sample3.jpg" alt=“画像その3"></li>
     </ul>
</div>

複数の機能を組み合わせる

JavaScriptで制御するためのclassを特定の要素に同時指定する場合、あまりに機能が多くなってくるとHTMLの記述が煩雑になるので、以下のように2つの機能を内容する機能を新たに定義する事ができます。

//2つの機能を併せ持つCarouselImageを新たに定義する
CarouselImage = function () {
     //.jsc-carouselimageが指定された要素に対して、各機能の処理を加える
     var $element = $('.jsc-carouselimage');

     new Carousel( $element );
     new ImagePreview( $element );
}
<div class="jsc-carouselimage">
     <ul>
          <li><img src="sample1.jpg” alt="画像その1"></li>
          <li><img src="sample2.jpg” alt="画像その2"></li>
          <li><img src="sample3.jpg” alt="画像その3"></li>
     </ul>
</div>

サンプルでは2機能を組み合わせていますが、実際には3〜4機能以上を同時指定する場合は組み合わせるイメージで良いと思います。

類似機能を実装する場合は機能を拡張する

いくつも機能を実装する場合、既に実装済みの機能と似たような機能のために既存の機能に対して+αで処理を加えたい場合があります。
他のプログラミング言語だと、継承という概念にあたるのですが、JavaScriptの場合はプロトタイプチェーンという機構を使って、ある機能を拡張して新しい機能を定義する事ができます。
たとえば、単一画像を拡大プレビューする機能があったとして、一部のページのみでは、複数画像をプレビュー内で切り替えるためのボタンを配置する必要があるとします。

単一画像のプレビューと複数画像のプレビュー

単一画像のプレビュー機能+αで複数画像のプレビュー機能を実現できそうですが、その際に単一画像のプレビュー処理をコピーペーストで複数画像のプレビュー処理を実装するのは保守性が下がるので避けるべきです。
そういう場合は既に定義された機能を拡張して必要な処理を流用します。ここではjQuery.extend関数を使用して機能を拡張しています。

//単一のプレビュー機能
ImagePreview.prototype = {
     onClick: function () {
          this.preview();
     },
     preview: function () {
         //画像をプレビューする処理
         ~~~ 割愛 ~~~
     }
};

//単一のプレビュー機能を拡張して複数プレビューする機能を定義する
ImageGroupPreview.prototype = $.extend({}, ImagePreview.prototype, {
     onClick: function () {
          this.groupPreview();
     },
     groupPreview: function () {
          //継承しているので、ImagePreviewのpreview関数を使用する事ができる。必要に応じて他の処理も実装する。
          this.preview();
     }
});

機能同士の連携はイベントを利用する

複数の機能を連携させたいケースはよくあります。たとえば、機能Aが動作した後に続けて機能Bを動作させるような場合です。
機能間で処理を連携させる場合はイベントを利用します。イベントは特定のプログラムが実行された事に対して、対応した処理を実行する形式ですが、イベントドリブンとも呼ばれます。イベントを利用する事で機能同士が疎結合になるので、プログラムが複雑になりにくくなり、複数の機能でも連携が行いやすくなります。

たとえば、モーダル画面を表示した直後に特定の位置までスクロールする機能があったとします。
その場合は以下のようにモーダルを表示する機能とスクロールする機能は別々に定義し、showModalイベントを通じて連携します。

//モーダルを表示する機能
Modal.prototype = {
     bindEvents: function () {
          //特定の要素をクリックした場合はモーダル画面を表示する
          $('jsc-modal-trigger').on('click', $.proxy( this.onClickTrigger, this) );
     },
     onClickModalTrigger: function () {
          //モーダル画面を表示する
          this.showModalWindow();
          //モーダル表示イベントを発火する
          $('window').trigger('showModal');
     }
}

//特定の位置にスクロールする機能
Scroll.prototype = {
     bindEvents: function () {
          //モーダル表示イベントに対応する関数を登録
          $('window').on( 'showModal', $.proxy( this.showModalEvent, this ) );
     },
     showModalEvent: function () {
          //モーダルが表示されたら、スクロールする
          this.scroll();
     }
}

機能同士を疎結合する事は汎用性を高めるうえでとても重要です。
共通機能の処理内に特定のページのみで使用する処理を含めるべきではありません。
もし、特定のページのみで処理を変えたい場合は、機能を拡張するなどをして、新たな機能を定義した方が安全です。

今回は僕自身が機能を設計する上で意識している事の一部を書きましたが、プログラミングの考え方は様々あり、もっと良い方法がないか日々模索中です。それでは!

Tweet about this on TwitterShare on FacebookShare on Google+Share on LinkedInEmail this to someone