一から勉強させてください( ̄ω ̄;)

最下級エンジニアが日々の学びをアウトプットしていくだけのブログです。

JavaScriptで関数の多重定義を行う方法

今回はJavaScriptで関数の多重定義を行う方法について書きます。


まず関数の多重定義というのは、何を引数に渡すか(引数の数、型など)によって異なる動作を行う関数を定義する方法です。

例えばjQuerycssメソッドとか良い例だとおもうんですが、

$(selector).css(name); //最初にマッチした要素の指定したCSSプロパティ値を返す
$(selector).css(name,value); //マッチしたすべての要素に指定したCSSプロパティを設定する


みたいな感じで、同じ名前のメソッドなのに何を渡すかによって挙動が変わります。


他のオブジェクト指向言語の場合、それぞれ異なるパラメータリストを持つ同じ名前のメソッドを別々に宣言すれば多重定義は簡単にできるのですが、JavaScriptではその方法は使えないです。
そのかわりファーストクラスオブジェクトである関数を利用しろと。


早速、JavaScriptで多重定義するサンプルを書いてみました。
(こちらでも見れます→http://jsfiddle.net/d_animal141/xh6Mj/embedded/result/


ちなみにこれはjQuery開発者でもある、ジョンレシグ氏のコードを丸パクリしてますw
http://ejohn.org/blog/javascript-method-overloading/

//html

<body>
    <div id="target1" style="width:100px; height:100px;"></div>
    <div id="target2" style="width:100px; height:100px;"></div>
    <div id="target3" style="width:100px; height:100px;"></div>
</body>
//js
    //多重定義用の関数
    function addMethod(object, name, fn) {
      var old = object[name];                        //#1
      
      object[name] = function(){
        console.log(arguments);
        console.log(fn.length);
        if (fn.length == arguments.length)           //#2
          return fn.apply(this, arguments)           //#2
        else if (typeof old == 'function')           //#3
          return old.apply(this, arguments);         //#3
      };
    }
    
    //コンストラクタ
    var Target = function(id){
      this.target = document.getElementById(id);
    };
    var p = Target.prototype;
    

    //changeColoeメソッドの多重定義
    addMethod(p, "changeColor", function(color_name){
      this.target.style.backgroundColor = color_name;
    });

    addMethod(p, "changeColor", function(r, g, b){
      this.target.style.backgroundColor = "rgb(" + r + "," + g + "," +  b + ")";
    });

    addMethod(p, "changeColor", function(r, g, b, a){
      this.target.style.backgroundColor = "rgba(" + r + "," + g + "," +  b + "," + a + ")";
    });
    

    //インスタンスつくってメソッド呼び出してみる
    var target1 = new Target("target1");
    target1.changeColor("red");

    var target2 = new Target("target2");
    target2.changeColor(255, 200, 100);
    
    var target3 = new Target("target3");
    target3.changeColor(155, 140, 100, 0.3);

あ、ちなみに引数の型調べたり、エラー処理したりとかは完全に無視してます。。

今回、多重定義を実装しているのはaddMethodという関数です。

これは関数自体がlengthプロパティを持つという特徴をうまく利用したテクで、
1回目のメソッドの登録は普通にobj[name] が登録されます。


次に2回目のメソッド登録ではまず変数oldに前回登録したobj[name]をキャッシュしてから、実際に呼び出される際のargumentsの数とfnの引数の数を比べるようにしています。
二つが一致してたらfnをそのまま呼び出し、もし違ってたらoldを呼び出します。


3回目も2回目と同様の流れで登録され、常にひとつ前に登録した処理をoldに格納したメソッドができあがります。


addMethodにconsoleを仕込んだらよくわかるとおもうのですが、
上記のサンプルコードでコンソールには

["red"] 
4
["red"]
3
["red"]
1 
[255, 200, 1000]
4
[255, 200, 1000]
3
[155, 140, 100, 0.3] 
4 


と表示され、changeColorメソッドの引数4こバージョン、3こバージョン、1こバージョンを順にたどって一致するものを呼び出して処理している事がわかります。


短い行の関数ですがこれはクロージャの仕組みをうまく使った深い実装法ですね。
さすがジョンレシグ氏だと思いました。


ただクロージャを使ってるってことはガベージコレクションがきかず、メモリ喰い続けてる関数がいるってことなので、実際に利用する際にはその辺も考慮しないといけないかもですね。。


あ、こちらの本もめっちゃおもしろいので、ぜひ参考にしてみてください。

JavaScript Ninjaの極意 ライブラリ開発のための知識とコーディング (Programmers’ SELECTION)

JavaScript Ninjaの極意 ライブラリ開発のための知識とコーディング (Programmers’ SELECTION)


小さな事からコツコツと。