JavaScript 黒魔術 - JS でも関数オーバーロードできるようにしよう

phi phi on javascript

オレの Advent Calendar 2015 - Adventar の 7 日目です.

C/C++ からプログラミングに入った方は,
JavaScript の柔軟な(自由すぎる)言語仕様に少々戸惑うかもしれません.

私もそうでした. とくに関数オーバーロードが無いのは, 大規模なライブラリを作る上でいつも悩まされています.

そこで名案というか, 苦肉の策というか, 黒魔術というか...

まぁとりあえず JavaScript でも関数オーバーロードっぽいことを カンタンにできるようにする仕組みを思いついたので紹介します.

こんなことやれるようにします.

var v = new Vector2();  
v.set(2, 4); // 2, 4 がセットされる  
v.set({x:4,y:8}); // 4, 8 がセットされる  
v.set("16,32"); // 16, 32 がセットされる  

Runstant Demo

まずはデモです. 数字だろうが, 文字列だろうが, オブジェクトだろうが 良い感じで値をセットしてくれているのが分かるかと思います.

引数オーバーロード可能なメソッドを定義するメソッドを定義しよう

Object.defineProperty(Object.prototype, "defineSpecialFunction", {  
  value: function(name, funcs) {
    var map = Array.prototype.map;

    Object.defineProperty(this, name, {
      value: function() {
        var types = map.call(arguments, function(elm) {
          return typeof elm;
        }).join(',');
        var fn = funcs[types];

        if (fn) {
          fn.apply(this, arguments);
        }
        else {
          console.error(types + " に対応するメソッドを定義してね♪");
        }
      }
    });
  }
});

defineSpecialFunction() という関数を定義しています.

内部でやってることは, 引数の全てのタイプを文字列化し連結し, それをキーとして対応するメソッドを実行しているだけです.

使い方は, 第1引数にメソッド名, 第2引数に型に対応したメソッドを 持ったオブジェクトを渡すと関数オーバーロードに対応したメソッドを定義できます.

defineSpecialFunction() の使い方

今回は試しにベクトルクラスを定義して使ってみます. ここでは defineSpecialFunction()set メソッドを定義します.

var Vector2 = function(x, y) {  
  this.x = x || 0;
  this.y = y || 0;
};

Vector2.prototype.print = function() {  
  console.log([this.x, this.y].join(','));
};

Vector2.prototype.defineSpecialFunction("set", {  
  'number,number': function(x, y) {
    this.x = x;
    this.y = y;
  },
  'object': function(v) {
    this.x = v.x;
    this.y = v.y;
  },
  'string': function(str) {
    var m = str.match(/(-?\d+(\.{1}\d+)?),\s*(-?\d+(\.{1}\d+)?)/);
    this.x = parseFloat(m[1]);
    this.y = parseFloat(m[3]);
  },
  'number,number,number': function() {
    alert('おいおい, オレは三次元ベクトルじゃないぞ!!')
  },
});

実際に set を使ってみましょう.

var v = new Vector2(0, 0);

// 数値2個だろうが
v.set(100, 200);  
v.print();      // 100,200

// オブジェクトだろうが
v.set({ x:2, y:4 });  
v.print();      // 2,4

// 文字列だろうが
v.set("4096,-128.5");  
v.print();      // 4096,-128.5

// 怒られます
v.set(1, 2, 3);  

ちゃんと引数の数, 型に対応したメソッドが呼ばれてますね!

最後に

実用性はゼロです. 他に良い関数名とかあったら教えて下さい.

あぁー, 仕事しよ...