More than 1 year has passed since last update.

JavaScript 黒魔術 - JS でも関数オーバーロードできるようにしよう | phiaryを読んで、もっと簡単にできそうだと思ったので自分で実装してみました。需要があるかは微妙ですけど…

サンプル

overloadFunc-test1.js
const testFunc1 = overloadFunc({
  'string, number': (a, b) => {
    console.log(`string ${a} and number ${b}!`);
  },
  'RegExp, string': (a, b) => {
    console.log(`RegExp ${a} and string ${b}!`);
  }
});

testFunc1('abc', 128);  // string abc and number 128!
testFunc1(/regexp/, 'xyz'); // RegExp /regexp/ and string xyz!

testFunc1('xyz', /regexp/); // Uncaught TypeError: Argument type does not match. Expected: (string, number), (RegExp, string)
overloadFunc-test2.js
const testFunc2 = overloadFunc({
  'Array, number': (a, b) => {
    console.log(`Array ${a} and number ${b}!`);
    console.log(`a[b]: ${a[b]}`);
  },
  'string, function': (a, b) => {
    console.log(`string ${a} and a function!`);

    b(a);
  },
  'default': (...args) => {
    console.log(`default :( args: ${args}`);
  }
});

testFunc2(['foo', 42, 'bar'], 1); // Array foo,42,bar and number 1! / a[b]: 42
testFunc2('baz', str => {
  console.log(`function called! ${str}`);
}); // string baz and a function! / function called! baz

testFunc2(32, 'def'); // default :( args: 32,def
overloadFunc-test3.js
const testFunc3 = overloadFunc({
  'boolean, string': (a, b) => {
    return `you can also return value! ${a} and ${b}`;
  },
  'default': (...args) => {
    return `default :( args: ${args}`;
  }
});

console.log(testFunc3(true, 'foo')); // you can also return value! true and foo

console.log(testFunc3(12, false)); // default :( args: 12,false

ソースコード

overloadFunc.js
const overloadFunc = (overloadDefinitions) => {

  // typeOf http://qiita.com/Hiraku/items/87e5d1cdaaa475c80cc2
  const typeOf = (x) => {
    if (x === null) return 'null';
    if (x == null) return 'undefined';
    const type = typeof x, c = x.constructor;
    if (type === 'number') {
      if (isNaN(x)) return 'NaN';
      if (!isFinite(x))
        return x === Infinity ? 'Infinity' : '-Infinity';
    }
    if (type === 'object') {
      return c && c.name ? c.name :
        Object.prototype.toString.call(x).slice(8, -1);
    }
    return type;
  };

  if(typeOf(overloadDefinitions) !== 'Object') throw new TypeError('overloadDefinitions must be an Object.');

  // オーバーロード関数の引数の型を配列に代入
  // const overloadArgTypes = Object.keys(overloadDefinitions).map(val => val.split(',').map(typeStr => typeStr.trim()));

  return (...args) => {
    // 引数の型を配列に代入
    const argTypes = args.map(arg => typeOf(arg));

    // オーバーロードの定義の中から引数の型とマッチするものを探す
    for(const key of Object.keys(overloadDefinitions)) {
      const overloadDefinitionArgTypes = key.split(',').map(type => type.trim());

      if(argTypes.toString() == overloadDefinitionArgTypes.toString()) {
        return overloadDefinitions[key](...args);
      }
    }

    // どの定義ともマッチしなかった場合
    if(overloadDefinitions['default']) {
      return overloadDefinitions['default'](...args);
    } else {
      throw new TypeError('Argument type does not match. Expected: ' + Object.keys(overloadDefinitions).map(val => '(' + val + ')').join(', '));
    }
  };
};

注意

  • 引数の型定義('Array, number'など)は大文字小文字を区別します。typeofを改善したtypeOf()関数 - Qiitaに書いてあるように指定して下さい。
  • ES6で書いてるのでIEでも使いたい場合はBabel通して下さい。

言語レベルでサポートされればもっと楽なのに…

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.