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通して下さい。
言語レベルでサポートされればもっと楽なのに…