JavaScriptの設計について考える - 複数の対象を効率良く制御する

はじめに

本稿ではJavaScriptにおける弊社の基本的な設計方針についてご紹介しています。前回はオブジェクトの保守性を高めるための設計方針について説明させて頂きました。但し前提条件として、制御対象が1つしかないという想定をしていました。このような場合は、オブジェクトやクロージャを用いて適切に設計することにより、コードの見通しが良くなり、保守性を高めることができます。今回は制御対象が複数ある場合の設計方法について説明致します。

制御対象が複数に増えた時の問題点

前回と同じように、カルーセルを作成する場合を例に挙げて説明することにします。まずはカルーセルを実装するためのHTMLとJavaScriptの設計例を再掲します。但し説明の便宜上、HTMLの構造やJavaScriptの処理内容を前回から少し変更しています。

HTMLその1

<div class="jsc-carousel-wrapper">
    <ul class="jsc-carousel-container">
        <li><img src="img/sample1.jpg" /></li>
        <li><img src="img/sample2.jpg" /></li>
        <li><img src="img/sample3.jpg" /></li>
    </ul>
    <input type="button" value="前へ" class="jsc-previous-trigger" />
    <input type="button" value="次へ" class="jsc-next-trigger" />
</div>
コードその1

var LEIHAUOLI = LEIHAUOLI || {};
LEIHAUOLI.SAMPLE_CODE = {};

LEIHAUOLI.SAMPLE_CODE.CAROUSEL = {
    SLIDE_INTERVAL : 5000,

    init : function(){
        this.setParameters();
        this.bindEvent();
        this.setTimer();
    },
    setParameters : function(){
        this.$wrapper = $('.jsc-carousel-wrapper');
        this.$container = this.$wrapper.find('.jsc-carousel-container');
        this.$previousTrigger = this.$wrapper.find('.jsc-previous-trigger');
        this.$nextTrigger = this.$wrapper.find('.jsc-next-trigger');
        this.imageWidth = this.$container.find('img').width();
    },
    bindEvent : function(){
         this.$previousTrigger.on('click', $.proxy(this.moveToPrevious, this));
         this.$nextTrigger.on('click', $.proxy(this.moveToNext, this));
    },
    setTimer : function(){
        setInterval($.proxy(this.moveToNext, this), this.SLIDE_INTERVAL);
    },
    moveToNext : function(){
        // ulタグ($container)を1画像分(imageWidth)左に移動させる
        ..........................
    },
    moveToPrevious : function(){
        //ulタグ($container)を1画像分(imageWidth)右に移動させる
        ..........................
    }
};

$(function(){
    LEIHAUOLI.SAMPLE_CODE.CAROUSEL.init();
});

上記のオブジェクトを実行することにより、カルーセルを適切に制御することができます。しかし、それは制御対象のカルーセルが1つしか存在しない場合です。それでは次のようにカルーセルの数を増やした場合に、上記のオブジェクトで制御することができるでしょうか?

HTMLその2

<!-- カルーセル1 -->
<div class="jsc-carousel-wrapper">
    <ul class="jsc-carousel-container">
        <li><img src="img/sample1.jpg" /></li>
        <li><img src="img/sample2.jpg" /></li>
        <li><img src="img/sample3.jpg" /></li>
    </ul>
    <input type="button" value="前へ" class="jsc-previous-trigger" />
    <input type="button" value="次へ" class="jsc-next-trigger" />
</div>
<!-- カルーセル2 -->
<div class="jsc-carousel-wrapper">
    <ul class="jsc-carousel-container">
        <li><img src="img/sample1.jpg" /></li>
        <li><img src="img/sample2.jpg" /></li>
        <li><img src="img/sample3.jpg" /></li>
    </ul>
    <input type="button" value="前へ" class="jsc-previous-trigger" />
    <input type="button" value="次へ" class="jsc-next-trigger" />
</div>

答えはNoです。

例えばユーザがカルーセル1の「次へ」ボタンをクリックした場合の挙動はどうなるでしょうか?期待する挙動は、カルーセル1の画像が左に動くことです。確かにこの期待は叶えられます。しかし都合の悪いことにカルーセル2の画像まで左に動いてしまいます。

何故このような挙動になるのでしょうか?上記のコードでは「次へ」ボタンの情報は変数$nextTriggerに、移動させるulタグの情報は変数$containerに格納しています。但しこれらの変数にはカルーセル1とカルーセル2の2つ分の情報が含まれています。その結果、どちらのカルーセルの「次へ」ボタンをクリックしても、両方のカルーセルが動いてしまうことになります。

複数の対象を制御するための考え方

操作された要素を判別する

1つのオブジェクトで2つのカルーセルを制御する場合、全ての処理が両者に対して実行されることになります。これには次のような処理が含まれます。

  • イベントハンドラの割り当て
  • 「次へ」ボタンがクリックされた時に画像を左に動かす処理
  • 「前へ」ボタンがクリックされた時に画像を右に動かす処理
  • 定期的に画像を左に動かす処理

イベントハンドラの割り当ては特に問題ありませんが、その他の処理を2つのカルーセルに対して実行してしまうと、前述のような不具合が発生します。これを防ぐためには、ユーザが何か操作する度に、どちらのカルーセルが操作されたのか判別する必要があります。この処理の実装例を次に示します。

コードその2
var LEIHAUOLI = LEIHAUOLI || {};
LEIHAUOLI.SAMPLE_CODE = {};

LEIHAUOLI.SAMPLE_CODE.CAROUSEL = {
    SLIDE_INTERVAL : 5000,

    init : function(){
        this.setParameters();
        this.bindEvent();
        this.setTimer();
    },
    setParameters : function(){
        this.$wrapper = $('.jsc-carousel-wrapper');
        this.$previousTrigger = this.$wrapper.find('.jsc-previous-trigger');
        this.$nextTrigger = this.$wrapper.find('.jsc-next-trigger');
    },
    bindEvent : function(){
         var myself = this;

         this.$previousTrigger.on('click', function(){
             // メソッドを実行する際に、クリックされたボタンの情報を渡す
             myself.moveToPrevious($(this));
         });
         this.$nextTrigger.on('click', function(){
             myself.moveToNext($(this));
         });
    },
    setTimer : function(){
        setInterval($.proxy(this.moveToNext, this), this.SLIDE_INTERVAL);
    },
    moveToNext : function($target){
        // クリックされたボタンの情報から、移動させるべきulタグの情報を取得する
        var $container = $target.parent().children('.jsc-carousel-container'),
           imageWidth = $container.children('img').width();
        // ulタグ($container)を1画像分(imageWidth)左に移動させる
        ..........................
    },
    moveToPrevious : function($target){
        var $container = $target.parent().children('.jsc-carousel-container'),
           imageWidth = $ontainer.children('img').width();
        // ulタグ($container)を1画像分(imageWidth)右に移動させる
        ..........................
    }
};

$(function(){
    LEIHAUOLI.SAMPLE_CODE.CAROUSEL.init();
});

上記のコードでは、ボタンがクリックされる度に操作されたボタンを判別し、それを元に移動させるべきulタグの情報を取得しています。これにより、2つのカルーセルを独立して操作することが可能になりますが、欠点もあります。それはイベントが発生する度にulタグの情報や画像の幅の情報を再取得していることです。これらの情報はカルーセルごとに異なりますが、操作対象が同じ場合は同じ結果になります。よってクリックする度に再取得するのは効率が悪くなります。しかし上記のコードでは、ボタンがクリックされるまで操作された要素を判別することができないため、このような記述方法にせざるを得なくなっています。これは$nextTriggerと$previousTriggerに各々2つのカルーセルのボタンの情報が含まれていることが原因です。

この問題は、次のようにボタンをカルーセルごとに分離してからイベントハンドラを設定することで解消することができます。

コードその3

var LEIHAUOLI = LEIHAUOLI || {};
LEIHAUOLI.SAMPLE_CODE = {};

LEIHAUOLI.SAMPLE_CODE.CAROUSEL = {
    SLIDE_INTERVAL : 5000,

    init : function(){
        this.setParameters();
        this.bindEvent();
        this.setTimer();
    },
    setParameters : function(){
        this.$wrapper = $('.jsc-carousel-wrapper');
        this.$previousTrigger = this.$wrapper.find('.jsc-previous-trigger');
        this.$nextTrigger = this.$wrapper.find('.jsc-next-trigger');
    },
    bindEvent : function(){
         var myself = this;

         // eachメソッドを使用して「前へ」ボタンを分離する
         this.$preivousTrigger.each(function(){
             // カルーセルを移動させるために必要な情報を変数に代入しておく
             var $container = $(this).parent().children('.jsc-carousel-container'),
                 imageWidth = $container.children('img').width();

             // イベントハンドラ設定時に、取得した情報を引数として渡す
             myself.$previousTrigger.on('click', function(){
                 myself.moveToPrevious($container, imageWidth);
             });
         });
         this.$nextTrigger.on('click', function(){
             var $container = $(this).parent().children('.jsc-carousel-container'),
                 imageWidth = $container.children('img').width();

             myself.$nextTrigger.on('click', function(){
                 myself.moveToNext($container, imageWidth);
             });
         });
    },
    setTimer : function(){
        setInterval($.proxy(this.moveToNext, this), this.SLIDE_INTERVAL);
    },
    moveToNext : function($container, imageWidth){
        // ulタグ($container)を1画像分(imageWidth)左に移動させる
        ..........................
    },
    moveToPrevious : function($target, imageWidth){
        // ulタグ($container)を1画像分(imageWidth)左に移動させる
        ..........................
    }
};

$(function(){
    LEIHAUOLI.SAMPLE_CODE.CAROUSEL.init();
});

このように予めボタンを分離してしまえば、ボタンがクリックされる前でも、ボタンと移動対象のulタグの情報を結びつけることができます。しかもeachメソッドの処理はイベントハンドラと異なり、ページが読み込まれた時に一度しか実行されません。これによりイベントが発生する度に同じ情報を再取得するような無駄な処理を省くことができます。しかし「コードその1」と比較した場合、bindEventメソッドが肥大化しています。設定するイベントハンドラの数が増えれば、その度合いは更に大きくなります。1つのメソッドの処理が長くなると、一般的にコードの見通しが悪くなり、保守性が下がることになります。

またbindEventというメソッド名にも関わらず、イベントの割り当て以外の処理が含まれてしまっています。$containerやimageWidthなどのパラメータの設定は、その名の通りsetParametersメソッドに記述した方が可読性が高くなります。

カルーセルごとに挙動をカスタマイズしたい場合は更に処理が複雑になります。例えばカルーセル1を5秒間隔で、カルーセル2を10秒間隔で移動させるような、ほんのちょっとしたカスタマイズでさえ手間がかかります。制御対象のカルーセルの数やカスタマイズの種類が増えると、その手間が増大することになります。

制御対象を1つに限定する

今まで述べてきたように、1つのオブジェクトで複数の対象を制御しようとすると、次のような問題が発生します。

  • 操作された要素を判別する処理が必要になるため、コードが複雑になる
  • 制御対象ごとに個別にカスタマイズしにくくなる

このような問題を回避するためにも、オブジェクトの制御対象は1つに限定した方が良いでしょう。複数の対象を制御する必要がある場合は、同じ数だけオブジェクトを「生成」して1つずつ制御させるようにします。

それでは、どうすれば複数のオブジェクトを「生成」することができるのでしょうか?例えば次のような方法が考えられます。まずカルーセルの一番外側のタグに対して、個別のクラスを付与します。

HTMLその3

<!-- カルーセル1 -->
<div class="jsc-carousel-wrapper1">
    <ul class="jsc-carousel-container">
        <li><img src="img/sample1.jpg" /></li>
        <li><img src="img/sample2.jpg" /></li>
        <li><img src="img/sample3.jpg" /></li>
    </ul>
    <input type="button" value="前へ" class="jsc-previous-trigger" />
    <input type="button" value="次へ" class="jsc-next-trigger" />
</div>
<!-- カルーセル2 -->
<div class="jsc-carousel-wrapper2">
    <ul class="jsc-carousel-container">
        <li><img src="img/sample1.jpg" /></li>
        <li><img src="img/sample2.jpg" /></li>
        <li><img src="img/sample3.jpg" /></li>
    </ul>
    <input type="button" value="前へ" class="jsc-previous-trigger" />
    <input type="button" value="次へ" class="jsc-next-trigger" />
</div>

上記の例では、カルーセルごとに各々「jsc-carousel-wrapper1」「jsc-carousel-wrapper2」という異なるクラスを付与しています。そして次のように各カルーセルごとに専用のオブジェクトを定義します。

コードその4

var LEIHAUOLI = LEIHAUOLI || {};
LEIHAUOLI.SAMPLE_CODE = {};

LEIHAUOLI.SAMPLE_CODE.CAROUSEL1 = {
    SLIDE_INTERVAL : 5000,

    init : function(){
        this.setParameters();
        this.bindEvent();
        this.setTimer();
    },
    setParameters : function(){
        this.$wrapper = $('.jsc-carousel-wrapper1');
        this.$container = this.$wrapper.find('.jsc-carousel-container');
        this.$previousTrigger = this.$wrapper.find('.jsc-previous-trigger');
        this.$nextTrigger = this.$wrapper.find('.jsc-next-trigger');
        this.imageWidth = this.$container.find('img').width();
    },
    ..................
};
LEIHAUOLI.SAMPLE_CODE.CAROUSEL2 = {
    SLIDE_INTERVAL : 10000,

    init : function(){
        this.setParameters();
        this.bindEvent();
        this.setTimer();
    },
    setParameters : function(){
        this.$wrapper = $('.jsc-carousel-wrapper2');
        this.$container = this.$wrapper.find('.jsc-carousel-container');
        this.$previousTrigger = this.$wrapper.find('.jsc-previous-trigger');
        this.$nextTrigger = this.$wrapper.find('.jsc-next-trigger');
        this.imageWidth = this.$container.find('img').width();
    },
    ..................
};

$(function(){
    LEIHAUOLI.SAMPLE_CODE.CAROUSEL1.init();
    LEIHAUOLI.SAMPLE_CODE.CAROUSEL2.init();
});

2つのオブジェクトは一か所を除いて完全に同じ構成になっています。唯一異なるのは、インスタンス変数$wrapperの参照先です。$wrapperは制御対象のカルーセルの要素を取得する際の起点になっているため、この要素の参照先を適切に変更するだけで制御対象を変更することができます。

しかしこれはオブジェクトの「定義」を増やしているだけで「生成」しているわけではありません。この方法の欠点は、同じオブジェクトを制御対象の数だけ定義しなければならないということです。このように同じオブジェクトを幾つも定義すると、次のような問題が発生します。

  • プログラム全体が不必要に長くなるため、可読性が下がる
  • バグの修正や機能追加をする場合、全てのオブジェクトに対して一律に行う必要があるため保守性が下がる

それでは次の方法はどうでしょうか?

コードその5

var LEIHAUOLI = LEIHAUOLI || {};
LEIHAUOLI.SAMPLE_CODE = {};

LEIHAUOLI.SAMPLE_CODE.CAROUSEL = function($wrapper){
    var SLIDE_INTERVAL = 5000,
        $container = $wrapper.find('.jsc-carousel-container'),
        $previousTrigger = this.$wrapper.find('.jsc-previous-trigger'),
        $nextTrigger = this.$wrapper.find('.jsc-next-trigger'),
        imageWidth = this.$container.find('img').width();

    var init = function(){
        bindEvent();
        setTimer();
    },
    bindEvent = function(){
        ................
    },
    setTimer = function(){
        ................
    },
    ..................
    return {
        init : init
    };
};

$(function(){
    var object1 = LEIHAUOLI.SAMPLE_CODE.CAROUSEL($('.jsc-carousel-wrapper1'));
    var object2 = LEIHAUOLI.SAMPLE_CODE.CAROUSEL($('.jsc-carousel-wrapper2'));
    object1.init();
    object2.init();
});

これは前回紹介したクロージャ設計を応用したものです。関数LEIHAUOLI.SAMPLE_CODE.CAROUSELは、実行するたびに「同じ形式のオブジェクト」を生成して返します。更に関数実行時に制御対象となるカルーセルの情報を引数として渡すことにより、制御対象を変更することができます。これにより「コードその4」のように同じ形式のオブジェクトを何度も定義する必要がなくなります。尚「同じ形式のオブジェクト」とは次の条件を満たすオブジェクトを指します。

  • インスタンス変数の名前が同じ(値は異なっても良い)
  • メソッドの名前と処理内容が同じ

「コードその5」は「コードその4」に比べて、可読性と保守性において、はるかに優れています。しかし残念ながら問題もあります。それは、オブジェクトを生成する度に、変数や関数のメモリ領域が割り当てられてしまうということです。変数のメモリ領域が毎回割り当てられるのは問題ありません。オブジェクトごとに制御するカルーセルが異なるため、変数のメモリ領域は、そもそも個別に割り当てられる必要があります。同じメモリ領域に割り当てられてしまったら、複数のカルーセルでその情報を共有することになるため、カルーセルの切り分けができなくなります。

しかしカルーセルの制御を行うための処理は、全てのカルーセルで共通であるため、関数のメモリ領域を個別に割り当てる必要はありません。しかも関数のメモリ領域は変数に比べて大きくなるため、生成するオブジェクトが増えると性能劣化を引き起こす要因になります。

コンストラクタを用いた設計

同じ形式のオブジェクトを効率よく生成するためにはコンストラクタと呼ばれる仕組みを利用します。コンストラクタは次のように記述します。

コードその6

var Constructor = function(){
};

これは関数の定義方法と同じです。今回は分かり易いように「Constructor」という名前を付けていますが、本来は自由に指定することができます。名前の最初の文字が大文字なのは、コンストラクタを定義する際の慣例のようなものであり、文法上必須ではありません。関数とコンストラクタの違いは、その実行方法にあります。

コードその7

var Constructor = function(){
};
var result1 = Constructor(); // 関数として実行
var result2 = new Constructor(); // コンストラクタとして実行

実行方法の違いは、実行時に「new」演算子を付与するかどうかです。newを付与せずに実行すれば関数に、newを付与して実行すればコンストラクタになるというわけです。「コードその7」のConstructorには何も処理を記述していないため、関数として実行した場合は値を返すことはありません。よってresult1はundefinedになります。しかしコンストラクタとして実行した場合、明示的に記述していなくても空のオブジェクトが自動的に生成され、実行結果として返されます。よってresult2には空のオブジェクトが格納されることになります。勿論、空のオブジェクトを生成するだけならば、コンストラクタを定義する必要はありません。次のように空のオブジェクトを直接代入すれば良いのです。

コードその8

var result2 = {};

しかしコンストラクタに対して適切に処理を記述すれば、任意の形式のオブジェクトを生成して返すことができるようになります。具体例を次に示します。

コードその9
var Constructor = function(){
    this.variable = 100;
    this.method = function(){
        console.log(this.variable);
    };
};
var object1 = new Constructor();

上記のobject1には、次に挙げるobject2と同じ形式のオブジェクトが代入されることになります。

コードその10
var object2 = {
    variabale : 100,
    method : function(){
        console.log(this.variable);
    }
};

コンストラクタの処理でポイントとなるのは「this」というキーワードです。thisはメソッド内で参照した時には、そのメソッドの所属するオブジェクトを指します。しかしコンストラクタ内で参照した時には、コンストラクタ実行時に自動的に生成されたオブジェクトを指します。よって「コードその9」のコンストラクタの処理は、生成されたオブジェクトに対して、インスタンス変数variableとメソッドmethodを追加していることになります。このオブジェクトは、コンストラクタの処理が全て終了してから返されるため、結果的にobject2と同じ形式になります。更に次のように記述することにより、オブジェクト生成時にインスタンス変数の初期値を変更することもできます。

コードその11
var Constructor = function(variable){
    this.variable = variable;
    this.method = function(){
        console.log(this.variable);
    };
};
var object1 = new Constructor(100);
var object2 = new Constructor(200);
object1.method(); // 100が表示される
object2.method(); // 200が表示される

上記の例では、インスタンス変数variableの初期値を各々100と200に設定した2つのオブジェクトを生成しています。コンストラクタを使用すれば、「コードその5」のクロージャと同様、1回定義するだけで、同じ形式のオブジェクトを幾つでも生成することができます。しかしこれだけならば、どちらの方法を使用しても大きな違いはありません。コンストラクタにも、オブジェクトを生成する度にインスタンス変数とメソッドのメモリ領域が割り当てられるという問題があるからです。しかしコンストラクタには、この問題を克服するための仕組みがあります。

コンストラクタはprototypeと呼ばれるプロパティを持っています。このプロパティに追加した情報は、そのコンストラクタから生成した全てのオブジェクトから参照することができます。しかもこの情報は、コンストラクタ内に記述した場合と異なり、オブジェクトを生成する度にメモリ領域が割り当てられることがありません。よってコンストラクタを定義する場合、次のようにインスタンス変数はコンストラクタ内で「this」に対して追加し、メソッドはprototypeプロパティに対して追加することで、メモリ領域の消費を抑えることができます。

コードその12
var Constructor = function(variable){
    this.variable = variable;
};
Constructor.prototype = {
    method : function(){
        console.log(this.variable);
    }
};
var object1 = new Constructor(100);
var object2 = new Constructor(200);
object1.method(); // 100が表示される
object2.method(); // 200が表示される

尚、prototypeプロパティに対してメソッドを追加する場合、上記のようにこれらの情報を含むオブジェクトを定義し、prototypeプロパティに対して代入します。「コードその1」をコンストラクタの形式に書き直すと次のようになります。

コードその13

var LEIHAUOLI = LEIHAUOLI || {};
LEIHAUOLI.SAMPLE_CODE = {};

LEIHAUOLI.SAMPLE_CODE.CAROUSEL = function($wrapper){
    this.$wrapper = $wrapper;
    this.init();
};
LEIHAUOLI.SAMPLE_CODE.CAROUSEL.prototype = {
    SLIDE_INTERVAL : 5000,

    init : function(){
        this.setParameters();
        this.bindEvent();
        this.setTimer();
    },
    setParameters : function(){
        this.$container = this.$wrapper.find('.jsc-carousel-container');
        this.$previousTrigger = this.$wrapper.find('.jsc-previous-trigger');
        this.$nextTrigger = this.$wrapper.find('.jsc-next-trigger');
        this.imageWidth = this.$container.find('img').width();
    },
    bindEvent : function(){
         this.$previousTrigger.on('click', $.proxy(this.moveToPrevious, this));
         this.$nextTrigger.on('click', $.proxy(this.moveToNext, this));
    },
    setTimer : function(){
        setInterval($.proxy(this.moveToNext, this), this.SLIDE_INTERVAL);
    },
    moveToNext : function(){
        // ulタグ($container)を1画像分(imageWidth)左に移動させる
        ..........................
    },
    moveToPrevious : function(){
        //ulタグ($container)を1画像分(imageWidth)右に移動させる
        ..........................
    }
};

$(function(){
    new LEIHAUOLI.SAMPLE_CODE.CAROUSEL($('.jsc-carousel-wrapper'));
});

上記のコードではコンストラクタの処理の最後でinitメソッドを実行しています。このように記述することにより、コンストラクタを実行するだけで、オブジェクトの生成とカルーセルの初期化を一括して行うことができます。尚、カルーセルのアニメーション間隔を指定するSLIDE_INTERVALは定数として用いているため、カルーセルごとにメモリ領域を割り当てる必要はありません。そのためコンストラクタ内ではなく、prototypeプロパティに追加しています。

しかし上記のコードを見ると、SLIDE_INTERVALだけではなく、$containerを始めとする他の変数も、コンストラクタ内ではなく、prototypeプロパティに追加したオブジェクト内で定義しています。これらの変数のメモリ領域は、全てのカルーセルから共有されないのでしょうか?確かに変数$containerを定義しているsetParametersメソッドのメモリ領域は共有されます。しかしsetParametersメソッド内で参照している「this」の実体がカルーセルごとに異なるため、メモリ領域も個別に割り当てられるのです。

「コードその1」と「コードその13」を比較してみてください。2つのコードはほとんど同じであることが分かります。異なるのは最初の数行と、最後の実行処理のみです。オブジェクトをコンストラクタに書き直す手間は、オブジェクトの規模が大きくなってもほどんど変わりません。慣れれば数分で書き直すことができます。一方、規模の大きなクロージャをコンストラクタに書き直すのは非常に大変です。また、クロージャとコンストラクタは記述形式が全く異なるため、混在させると可読性が下がります。

それでは「コード13」のコンストラクタを使用して複数のカルーセルを制御するにはどのように記述すれば良いのでしょうか?「HTMLその2」のように制御対象の全てのカルーセルに対して同じクラスを付与している場合は、コンストラクタの実行処理を次のように変更するだけで対応することができます。

コードその14

$(function(){
    $('.jsc-carousel-wrapper').each(function(){
        new LEIHAUOLI.SAMPLE_CODE.CAROUSEL($(this));
    });
});

$(‘.jsc-carousel-wrapper’)には2つのカルーセルの要素が格納されています。eachメソッドを使用することにより、この要素をカルーセルごとに分離してコンストラクタの引数として渡しています。このように記述することにより、制御対象の数に関わらず、各カルーセルを独立して制御することができるようになります。

前回述べたように、オブジェクト設計とクロージャ設計を比較した場合、コードの堅牢さを考えるならばクロージャ設計の方が優れています。しかし実務では制御対象が複数になることが少なくないため、コンストラクタの使用を余儀なくされることが多くなります。コンストラクタと併用することを考えた場合、筆者はクロージャ設計よりも、コンストラクタとの親和性が高いオブジェクト設計の方が可読性と保守性が高くなると考えています。

終わりに

今回は複数の対象を効率良く制御することのできる、コンストラクタの設計方法について紹介しました。保守性の高いコードを記述するためには、オブジェクト設計とコンストラクタ設計を適切に使い分けることが重要になります。しかし、これは飽く迄も設計の基本でしかありません。複雑な仕様を持つ機能を設計する場合、1つのオブジェクトだけで制御することが難しいこともあります。次回は比較的複雑な機能に対して、複数のオブジェクトを連携して制御する設計例を紹介する予定です。

関連記事