3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaScriptで簡単に関数のオーバーロード

Posted at

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通して下さい。

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

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?