Javascriptで偽の関数をオーバーロードするための最善の方法は何ですか?
他の言語のようにJavascriptで関数をオーバーロードすることは不可能であることを私は知っています。私が2つの関数foo(x)
とfoo(x,y,z)
を使った関数が必要な場合、これが最善の方法です。
y = y || 'default'
のようなオプションの引数を使うパラメータを使って関数のオーバーロードを行う最善の方法は、引数の長さや型をチェックしないことです。型をチェックするだけでコードが遅くなり、配列、null、オブジェクトなどの面白さが得られます。
ほとんどの開発者がすることは彼らのメソッドへの最後の引数としてオブジェクトに取り組むことです。このオブジェクトは何でも保持できます。
function foo(a, b, opts) {
// ...
if (opts['test']) { } //if test param exists, do something..
}
foo(1, 2, {"method":"add"});
foo(3, 4, {"test":"equals", "bar":"tree"});
それからあなたはあなたの方法であなたが望むとにかくそれを扱うことができます。 [switch、if-elseなど]
私はよくこれをします:
C#:
public string CatStrings(string p1) {return p1;}
public string CatStrings(string p1, int p2) {return p1+p2.ToString();}
public string CatStrings(string p1, int p2, bool p3) {return p1+p2.ToString()+p3.ToString();}
CatStrings("one"); // result = one
CatStrings("one",2); // result = one2
CatStrings("one",2,true); // result = one2true
同等のJavaScript:
function CatStrings(p1, p2, p3)
{
var s = p1;
if(typeof p2 !== "undefined") {s += p2;}
if(typeof p3 !== "undefined") {s += p3;}
return s;
};
CatStrings("one"); // result = one
CatStrings("one",2); // result = one2
CatStrings("one",2,true); // result = one2true
この特定の例は、C#よりもJavaScriptのほうが実際より洗練されています。指定されていないパラメータは、javascriptでは 'undefined'であり、if文ではfalseと評価されます。ただし、関数定義は、p2とp3がオプションであるという情報を伝えません。大量のオーバーロードが必要な場合は、jQueryはパラメータとしてオブジェクトを使用することにしました。たとえば、jQuery.ajax(options)です。私は、これがオーバーロードに対する最も強力で明確に文書化可能なアプローチであることに同意しますが、1つか2つ以上のクイックオプションパラメータを必要とすることはめったにありません。
編集:イアンの提案に従ってIFテストを変更
JavaScriptでは、あらゆる種類のパラメータをいくつでも渡すことができるため、実際の関数のオーバーロードはありません。あなたは関数の中でいくつの arguments が渡されたか、そしてそれらがどんな型であるかをチェックしなければなりません。
正解は、THERE IS JAVASCRIPTにはオーバーロードしません。
関数内のチェック/切り替えはオーバーロードではありません。
オーバーロードの概念:一部のプログラミング言語では、関数オーバーロードまたはメソッドオーバーロードは、異なる実装で同じ名前の複数のメソッドを作成する機能です。オーバーロードされた関数を呼び出すと、呼び出しのコンテキストに適したその関数の特定の実装が実行され、1つの関数呼び出しでコンテキストに応じて異なるタスクを実行できます。
例えば、doTask()とdoTask(オブジェクトO)はオーバーロードされたメソッドです。後者を呼び出すには、オブジェクトをパラメータとして渡す必要がありますが、前者はパラメータを必要とせず、空のパラメータフィールドで呼び出されます。一般的なエラーは、2番目のメソッドでオブジェクトにデフォルト値を割り当てることです。これは、2つのメソッドのどちらを使用するかコンパイラが認識しないため、あいまいな呼び出しエラーになります。
https://en.wikipedia.org/wiki/Function_overloading
提案された実装はすべて素晴らしいですが、真実を言うと、JavaScriptのネイティブ実装はありません。
これにもっと近づく方法は2つあります。
柔軟性を残したい場合は、辞書(連想配列)を渡してください。
引数としてオブジェクトを取り、柔軟性を追加するためにプロトタイプベースの継承を使います。
以下に示すように、パラメータ型を使用して実際のメソッドをオーバーロードできるようにする方法があります。
Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point());
Func(0, 0, 0, 0);
Edit(2018):これは2011年に書かれて以来、直接メソッド呼び出しの速度は非常に速くなりましたが、オーバーロードされたメソッドの速度はそうではありませんでした。
これは私がお勧めするアプローチではありませんが、この種の問題をどのように解決できるかを考えることは価値のある思考の練習です。
これが、さまざまなアプローチのベンチマークです - https://jsperf.com/function-overloading 。これは、関数のオーバーロード(型を考慮に入れる)は、16.0(ベータ)の時点で、Google ChromeのV8では約13倍遅くなることを示しています。
オブジェクト({x: 0, y: 0}
)を渡すのと同様に、適切な場合にはCのアプローチを取り、それに応じてメソッドに名前を付けることもできます。たとえば、Vector.AddVector(vector)、Vector.AddIntegers(x、y、z、...)、Vector.AddArray(integerArray)などです。命名のインスピレーションを得るのにOpenGLのようなCライブラリを見ることができます。
Edit:'param' in arg
とarg.hasOwnProperty('param')
の両方を使ってオブジェクトを渡してテストするためのベンチマークを追加しました。関数のオーバーロードはオブジェクトを渡してプロパティをチェックするよりもはるかに高速です(少なくともこのベンチマークで)。
設計の観点からは、関数のオーバーロードは、オーバーロードされたパラメータが同じアクションに対応する場合にのみ有効または論理的です。そのため、特定の詳細にのみ関係する基本的な方法があるはずであり、そうでなければ不適切なデザインの選択を示している可能性があります。そのため、データをそれぞれのオブジェクトに変換することによって、関数のオーバーロードの使用を解決することもできます。もちろん名前を印刷することだけを目的としている場合は、複雑なデザインを作成する必要はないため、問題の範囲を考慮する必要がありますが、フレームワークやライブラリのデザインについては、そのような考えは正当です。
私の例はRectangleの実装から来ています - それゆえDimensionとPointの言及です。おそらく、RectangleはDimension
およびPoint
プロトタイプにGetRectangle()
メソッドを追加することができ、それから関数のオーバーロードの問題は解決されます。そしてプリミティブはどうですか?さて、引数の長さがあります。オブジェクトにはGetRectangle()
メソッドがあるので、これは現在有効なテストです。
function Dimension() {}
function Point() {}
var Util = {};
Util.Redirect = function (args, func) {
'use strict';
var REDIRECT_ARGUMENT_COUNT = 2;
if(arguments.length - REDIRECT_ARGUMENT_COUNT !== args.length) {
return null;
}
for(var i = REDIRECT_ARGUMENT_COUNT; i < arguments.length; ++i) {
var argsIndex = i-REDIRECT_ARGUMENT_COUNT;
var currentArgument = args[argsIndex];
var currentType = arguments[i];
if(typeof(currentType) === 'object') {
currentType = currentType.constructor;
}
if(typeof(currentType) === 'number') {
currentType = 'number';
}
if(typeof(currentType) === 'string' && currentType === '') {
currentType = 'string';
}
if(typeof(currentType) === 'function') {
if(!(currentArgument instanceof currentType)) {
return null;
}
} else {
if(typeof(currentArgument) !== currentType) {
return null;
}
}
}
return [func.apply(this, args)];
}
function FuncPoint(point) {}
function FuncDimension(dimension) {}
function FuncDimensionPoint(dimension, point) {}
function FuncXYWidthHeight(x, y, width, height) { }
function Func() {
Util.Redirect(arguments, FuncPoint, Point);
Util.Redirect(arguments, FuncDimension, Dimension);
Util.Redirect(arguments, FuncDimensionPoint, Dimension, Point);
Util.Redirect(arguments, FuncXYWidthHeight, 0, 0, 0, 0);
}
Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point());
Func(0, 0, 0, 0);
最善の方法は、実際には関数と引数によって異なります。あなたの選択肢のそれぞれは異なる状況では良い考えです。私は一般的にそれらのうちの1つがうまくいくまで以下の順序でこれらを試みます:
y = y ||のようなオプションの引数を使う'デフォルト'。 あなたがそれをすることができればこれは便利ですが、それは常に実用的に機能するとは限りません。 0/null/undefinedが有効な引数になります。
多数の引数を使用する。 最後のオプションと似ていますが、#1が機能しないときに機能する可能性があります。
引数の種類を確認する。 これは、引数の数が同じ場合には有効です。確実にタイプを判断できない場合は、異なる名前を使用する必要があります。
そもそも別の名前を使う。 他のオプションが機能しない、実用的でない、または他の関連機能との一貫性を保つためにこれを行う必要があるかもしれません。
もし私が2つの関数foo(x)とfoo(x、y、z)を必要とするなら、これは最善の方法です。
問題は、JavaScriptがメソッドのオーバーロードをネイティブにサポートしていないことです。したがって、同じ名前の2つ以上の関数を見たり解析したりする場合は、最後に定義された関数を考慮して前の関数を上書きするだけです。
私はほとんどの場合に適していると思う方法の一つは以下の通りです -
方法があるとしましょう
function foo(x)
{
}
メソッドをオーバーロードする代わりに これはJavaScriptでは不可能です あなたは新しいメソッドを定義することができます
fooNew(x,y,z)
{
}
そして、次のように1st関数を修正します -
function foo(arguments)
{
if(arguments.length==2)
{
return fooNew(arguments[0], arguments[1]);
}
}
このようなオーバーロードされたメソッドが多数ある場合は、単なるif-else
ステートメントよりもswitch
を使用することを検討してください。
( 詳細 )
シモンズ:上記のリンクは私の個人的なブログに追加の詳細があります。
ベストプラクティスについてはよくわかりませんが、その方法は次のとおりです。
/*
* Object Constructor
*/
var foo = function(x) {
this.x = x;
};
/*
* Object Protoype
*/
foo.prototype = {
/*
* f is the name that is going to be used to call the various overloaded versions
*/
f: function() {
/*
* Save 'this' in order to use it inside the overloaded functions
* because there 'this' has a different meaning.
*/
var that = this;
/*
* Define three overloaded functions
*/
var f1 = function(arg1) {
console.log("f1 called with " + arg1);
return arg1 + that.x;
}
var f2 = function(arg1, arg2) {
console.log("f2 called with " + arg1 + " and " + arg2);
return arg1 + arg2 + that.x;
}
var f3 = function(arg1) {
console.log("f3 called with [" + arg1[0] + ", " + arg1[1] + "]");
return arg1[0] + arg1[1];
}
/*
* Use the arguments array-like object to decide which function to execute when calling f(...)
*/
if (arguments.length === 1 && !Array.isArray(arguments[0])) {
return f1(arguments[0]);
} else if (arguments.length === 2) {
return f2(arguments[0], arguments[1]);
} else if (arguments.length === 1 && Array.isArray(arguments[0])) {
return f3(arguments[0]);
}
}
}
/*
* Instantiate an object
*/
var obj = new foo("z");
/*
* Call the overloaded functions using f(...)
*/
console.log(obj.f("x")); // executes f1, returns "xz"
console.log(obj.f("x", "y")); // executes f2, returns "xyz"
console.log(obj.f(["x", "y"])); // executes f3, returns "xy"
私はちょうどこれを試した、多分それはあなたのニーズに合っている。引数の数に応じて、異なる関数にアクセスすることができます。初めて呼び出すときに初期化します。そして関数マップはクロージャに隠されています。
TEST = {};
TEST.multiFn = function(){
// function map for our overloads
var fnMap = {};
fnMap[0] = function(){
console.log("nothing here");
return this; // support chaining
}
fnMap[1] = function(arg1){
// CODE here...
console.log("1 arg: "+arg1);
return this;
};
fnMap[2] = function(arg1, arg2){
// CODE here...
console.log("2 args: "+arg1+", "+arg2);
return this;
};
fnMap[3] = function(arg1,arg2,arg3){
// CODE here...
console.log("3 args: "+arg1+", "+arg2+", "+arg3);
return this;
};
console.log("multiFn is now initialized");
// redefine the function using the fnMap in the closure
this.multiFn = function(){
fnMap[arguments.length].apply(this, arguments);
return this;
};
// call the function since this code will only run once
this.multiFn.apply(this, arguments);
return this;
};
試して。
TEST.multiFn("0")
.multiFn()
.multiFn("0","1","2");
JavaScriptには関数オーバーロードオプションがないので、代わりにobjectを使うことができます。必要な引数が1つか2つある場合は、それらをoptionsオブジェクトから分離した方がよいでしょう。これは、optionsオブジェクトに値が渡されなかった場合に、optionsオブジェクトと値をデフォルト値に設定する方法の例です。
function optionsObjectTest(x, y, opts) {
opts = opts || {}; // default to an empty options object
var stringValue = opts.stringValue || "string default value";
var boolValue = !!opts.boolValue; // coerces value to boolean with a double negation pattern
var numericValue = opts.numericValue === undefined ? 123 : opts.numericValue;
return "{x:" + x + ", y:" + y + ", stringValue:'" + stringValue + "', boolValue:" + boolValue + ", numericValue:" + numericValue + "}";
}
ここ はoptionsオブジェクトの使い方の例です
JavaScriptで関数のオーバーロードをする方法はありません。それで、私は多重定義の代わりにtypeof()
メソッドによって次のようにオーバーロードを偽造することを勧めます。
function multiTypeFunc(param)
{
if(typeof param == 'string') {
alert("I got a string type parameter!!");
}else if(typeof param == 'number') {
alert("I got a number type parameter!!");
}else if(typeof param == 'boolean') {
alert("I got a boolean type parameter!!");
}else if(typeof param == 'object') {
alert("I got a object type parameter!!");
}else{
alert("error : the parameter is undefined or null!!");
}
}
がんばろう!
前書き
これまで多くの答えを読んでいると、だれにでも頭痛がします。概念を知りたいと思う人は誰でも 以下の前提条件 sを知る必要があるでしょう。
Function overloading Definition
、Function Length property
、Function argument property
最も単純な形式のFunction overloading
は、関数に渡される引数の数に基づいて関数がさまざまなタスクを実行することを意味します。特に、TASK1、TASK2、およびTASK3は以下で強調表示されており、同じ関数arguments
に渡されるfooYo
の数に基づいて実行されています。
// if we have a function defined below
function fooYo(){
// do something here
}
// on invoking fooYo with different number of arguments it should be capable to do different things
fooYo(); // does TASK1
fooYo('sagar'); // does TASK2
fooYo('sagar','munjal'); // does TAKS3
_ note _ - JSは、関数オーバーロードの組み込み機能を提供しません。
代替
John E Resig(JSの作成者)は、関数のオーバーロードを実装する機能を実現するために上記の前提条件を使用する代替案を指摘しました。
以下のコードは、if-else
またはswitch
ステートメントを使用して、単純明快なアプローチを使用しています。
argument-length
プロパティを評価します。var ninja = {
whatever: function() {
switch (arguments.length) {
case 0:
/* do something */
break;
case 1:
/* do something else */
break;
case 2:
/* do yet something else */
break;
//and so on ...
}
}
}
もう一つのテクニックはもっとクリーンでダイナミックです。このテクニックのハイライトはaddMethod
ジェネリック関数です。
同じ名前 しかし 異なる機能 を持つオブジェクトに異なる関数を追加するために使用される関数addMethod
を定義します。
addMethod
関数の下には、3つのparamsオブジェクト名object
、関数名name
、および呼び出したい関数fn
を受け入れます。
addMethod
定義の内部var old
は、クロージャーの助けを借りて格納されている前のfunction
への参照を保護するためのバブルです。function addMethod(object, name, fn) {
var old = object[name];
object[name] = function(){
if (fn.length == arguments.length)
return fn.apply(this, arguments)
else if (typeof old == 'function')
return old.apply(this, arguments);
};
};
addMethod
の下には、ninja.whatever(x)
を使用して呼び出されたときに引数の数がx
である3つの関数が追加されます。空白または1つ以上は、addMethod
関数を使用しながら定義どおりに異なる関数を呼び出す。var ninja = {};
debugger;
addMethod(ninja,'whatever',function(){ console.log("I am the one with ZERO arguments supplied") });
addMethod(ninja,'whatever',function(a){ console.log("I am the one with ONE arguments supplied") });
addMethod(ninja,'whatever',function(a,b){ console.log("I am the one with TWO arguments supplied") });
ninja.whatever();
ninja.whatever(1,2);
ninja.whatever(3);
これにアプローチする別の方法は特別な変数を使うことです: arguments 、これは実装です:
function sum() {
var x = 0;
for (var i = 0; i < arguments.length; ++i) {
x += arguments[i];
}
return x;
}
したがって、このコードを次のように修正することができます。
function sum(){
var s = 0;
if (typeof arguments[0] !== "undefined") s += arguments[0];
.
.
.
return s;
}
これをチェックしてください。とてもかっこいいです。 http://ejohn.org/blog/javascript-method-overloading/ /トリックJavascriptを使用すると、次のような通話ができます。
var users = new Users();
users.find(); // Finds all
users.find("John"); // Finds users by name
users.find("John", "Resig"); // Finds users by first and last name
この記事にはすでにさまざまな解決策がたくさん含まれているので、もう1つ解決策を投稿することを考えました。
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
function overload() {
var functions = arguments;
var nroffunctionsarguments = [arguments.length];
for (var i = 0; i < arguments.length; i++) {
nroffunctionsarguments[i] = arguments[i].length;
}
var unique = nroffunctionsarguments.filter(onlyUnique);
if (unique.length === arguments.length) {
return function () {
var indexoffunction = nroffunctionsarguments.indexOf(arguments.length);
return functions[indexoffunction].apply(this, arguments);
}
}
else throw new TypeError("There are multiple functions with the same number of parameters");
}
これは以下のように使うことができます。
var createVector = overload(
function (length) {
return { x: length / 1.414, y: length / 1.414 };
},
function (a, b) {
return { x: a, y: b };
},
function (a, b,c) {
return { x: a, y: b, z:c};
}
);
console.log(createVector(3, 4));
console.log(createVector(3, 4,5));
console.log(createVector(7.07));
この解決策は完璧ではありませんが、私はそれがどのように行われることができるかを実証したいだけです。
ポリフィルなしでECMAScript 2018で関数のオーバーロードを行い、変数の長さ/タイプなどをチェックできるようになりました。spread syntax .
function foo(var1, var2, opts){
// set default values for parameters
const defaultOpts = {
a: [1,2,3],
b: true,
c: 0.3289,
d: "str",
}
// merge default and passed-in parameters
// defaultOpts must go first!
const mergedOpts = {...defaultOpts, ...opts};
// you can now refer to parameters like b as mergedOpts.b,
// or just assign mergedOpts.b to b
console.log(mergedOpts.a);
console.log(mergedOpts.b);
console.log(mergedOpts.c);
console.log(mergedOpts.d);
}
// the parameters you passed in override the default ones
// all JS types are supported: primitives, objects, arrays, functions, etc.
let var1, var2="random var";
foo(var1, var2, {a: [1,2], d: "differentString"});
// parameter values inside foo:
//a: [1,2]
//b: true
//c: 0.3289
//d: "differentString"
ECMAScriptプロポーザルのレスト/スプレッドプロパティ(ステージ4)は、スプレッドプロパティをオブジェクトリテラルに追加します。提供されたオブジェクトから新しいオブジェクトに列挙可能なプロパティをコピーします。 mdn の詳細
注:オブジェクトリテラルのスプレッド構文は、EdgeおよびIEでは機能せず、実験的な機能です。 ブラウザの互換性を見る
John Resigの 'addMethod'を使用できます。このメソッドを使用すると、引数の数に基づいてメソッドを「オーバーロード」できます。
// addMethod - By John Resig (MIT Licensed)
function addMethod(object, name, fn){
var old = object[ name ];
object[ name ] = function(){
if ( fn.length == arguments.length )
return fn.apply( this, arguments );
else if ( typeof old == 'function' )
return old.apply( this, arguments );
};
}
私はまた、キャッシングを使用して関数のバリエーションを保持するこの方法の代替手段を作成しました。 違いはここで説明されています
// addMethod - By Stavros Ioannidis
function addMethod(obj, name, fn) {
obj[name] = obj[name] || function() {
// get the cached method with arguments.length arguments
var method = obj[name].cache[arguments.length];
// if method exists call it
if ( !! method)
return method.apply(this, arguments);
else throw new Error("Wrong number of arguments");
};
// initialize obj[name].cache
obj[name].cache = obj[name].cache || {};
// Check if a method with the same number of arguments exists
if ( !! obj[name].cache[fn.length])
throw new Error("Cannot define multiple '" + name +
"' methods with the same number of arguments!");
// cache the method with fn.length arguments
obj[name].cache[fn.length] = function() {
return fn.apply(this, arguments);
};
}
これは、isFn
、isArr
などの型チェック関数を含む、より大きなコードからのものです。以下のVanillaJSバージョンは、すべての外部依存関係を削除するように書き直されましたが、.add()
呼び出しで使用するための独自の型チェック関数を定義する必要があります。
注: これは自己実行関数なので(クロージャ/クローズドスコープを持つことができます)、したがってfunction overload() {...}
ではなくwindow.overload
への代入です。
window.overload = function () {
"use strict"
var a_fnOverloads = [],
_Object_prototype_toString = Object.prototype.toString
;
function isFn(f) {
return (_Object_prototype_toString.call(f) === '[object Function]');
} //# isFn
function isObj(o) {
return !!(o && o === Object(o));
} //# isObj
function isArr(a) {
return (_Object_prototype_toString.call(a) === '[object Array]');
} //# isArr
function mkArr(a) {
return Array.prototype.slice.call(a);
} //# mkArr
function fnCall(fn, vContext, vArguments) {
//# <ES5 Support for array-like objects
//# See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Browser_compatibility
vArguments = (isArr(vArguments) ? vArguments : mkArr(vArguments));
if (isFn(fn)) {
return fn.apply(vContext || this, vArguments);
}
} //# fnCall
//#
function registerAlias(fnOverload, fn, sAlias) {
//#
if (sAlias && !fnOverload[sAlias]) {
fnOverload[sAlias] = fn;
}
} //# registerAlias
//#
function overload(vOptions) {
var oData = (isFn(vOptions) ?
{ default: vOptions } :
(isObj(vOptions) ?
vOptions :
{
default: function (/*arguments*/) {
throw "Overload not found for arguments: [" + mkArr(arguments) + "]";
}
}
)
),
fnOverload = function (/*arguments*/) {
var oEntry, i, j,
a = arguments,
oArgumentTests = oData[a.length] || []
;
//# Traverse the oArgumentTests for the number of passed a(rguments), defaulting the oEntry at the beginning of each loop
for (i = 0; i < oArgumentTests.length; i++) {
oEntry = oArgumentTests[i];
//# Traverse the passed a(rguments), if a .test for the current oArgumentTests fails, reset oEntry and fall from the a(rgument)s loop
for (j = 0; j < a.length; j++) {
if (!oArgumentTests[i].tests[j](a[j])) {
oEntry = undefined;
break;
}
}
//# If all of the a(rgument)s passed the .tests we found our oEntry, so break from the oArgumentTests loop
if (oEntry) {
break;
}
}
//# If we found our oEntry above, .fn.call its .fn
if (oEntry) {
oEntry.calls++;
return fnCall(oEntry.fn, this, a);
}
//# Else we were unable to find a matching oArgumentTests oEntry, so .fn.call our .default
else {
return fnCall(oData.default, this, a);
}
} //# fnOverload
;
//#
fnOverload.add = function (fn, a_vArgumentTests, sAlias) {
var i,
bValid = isFn(fn),
iLen = (isArr(a_vArgumentTests) ? a_vArgumentTests.length : 0)
;
//#
if (bValid) {
//# Traverse the a_vArgumentTests, processinge each to ensure they are functions (or references to )
for (i = 0; i < iLen; i++) {
if (!isFn(a_vArgumentTests[i])) {
bValid = _false;
}
}
}
//# If the a_vArgumentTests are bValid, set the info into oData under the a_vArgumentTests's iLen
if (bValid) {
oData[iLen] = oData[iLen] || [];
oData[iLen].Push({
fn: fn,
tests: a_vArgumentTests,
calls: 0
});
//#
registerAlias(fnOverload, fn, sAlias);
return fnOverload;
}
//# Else one of the passed arguments was not bValid, so throw the error
else {
throw "poly.overload: All tests must be functions or strings referencing `is.*`.";
}
}; //# overload*.add
//#
fnOverload.list = function (iArgumentCount) {
return (arguments.length > 0 ? oData[iArgumentCount] || [] : oData);
}; //# overload*.list
//#
a_fnOverloads.Push(fnOverload);
registerAlias(fnOverload, oData.default, "default");
return fnOverload;
} //# overload
//#
overload.is = function (fnTarget) {
return (a_fnOverloads.indexOf(fnTarget) > -1);
} //# overload.is
return overload;
}();
呼び出し元は、overload()
の戻り値に変数を代入することによって、オーバーロードされた関数を定義します。連鎖のおかげで、追加のオーバーロードは連続して定義できます。
var myOverloadedFn = overload(function(){ console.log("default", arguments) })
.add(function(){ console.log("noArgs", arguments) }, [], "noArgs")
.add(function(){ console.log("str", arguments) }, [function(s){ return typeof s === 'string' }], "str")
;
overload()
の単一のオプション引数は、シグネチャが識別できない場合に呼び出す「デフォルト」関数を定義します。 .add()
の引数は以下のとおりです。
fn
:オーバーロードを定義するfunction
。a_vArgumentTests
:Array
上で実行するテストを定義するfunction
sのarguments
。各function
は単一の引数を受け入れ、その引数が有効かどうかに基づいてtrue
thyを返します。sAlias
(オプション):オーバーロード関数(string
)に直接アクセスするためのエイリアスを定義するfn
。 myOverloadedFn.noArgs()
はその関数を直接呼び出し、引数の動的多態性テストを避けます。.add()
の2番目のa_vArgumentTests
引数は実際にはカスタム型を定義しているので、この実装は実際には単なる伝統的な関数オーバーロード以上のことを可能にします。そのため、型だけでなく範囲、値、または値の集合に基づいて引数をゲート制御できます。
overload()
の145行のコードを見ると、各シグネチャは渡されたarguments
の数によって分類されていることがわかります。これは、実行しているテストの数を制限するために行われています。私はまた呼び出し回数を追跡します。いくつかの追加コードを使用して、オーバーロードされた関数の配列を並べ替えて、一般的に呼ばれる関数が最初にテストされるようにすることもできます。
今、いくつかの注意点があります... Javascriptは大まかに型付けされているので、vArgumentTests
はinteger
として検証される可能性があるため、float
には注意が必要です。
JSCompress.comのバージョン(1114バイト、744バイトのG圧縮):
window.overload=function(){'use strict';function b(n){return'[object Function]'===m.call(n)}function c(n){return!!(n&&n===Object(n))}function d(n){return'[object Array]'===m.call(n)}function e(n){return Array.prototype.slice.call(n)}function g(n,p,q){if(q=d(q)?q:e(q),b(n))return n.apply(p||this,q)}function h(n,p,q){q&&!n[q]&&(n[q]=p)}function k(n){var p=b(n)?{default:n}:c(n)?n:{default:function(){throw'Overload not found for arguments: ['+e(arguments)+']'}},q=function(){var r,s,t,u=arguments,v=p[u.length]||[];for(s=0;s<v.length;s++){for(r=v[s],t=0;t<u.length;t++)if(!v[s].tests[t](u[t])){r=void 0;break}if(r)break}return r?(r.calls++,g(r.fn,this,u)):g(p.default,this,u)};return q.add=function(r,s,t){var u,v=b(r),w=d(s)?s.length:0;if(v)for(u=0;u<w;u++)b(s[u])||(v=_false);if(v)return p[w]=p[w]||[],p[w].Push({fn:r,tests:s,calls:0}),h(q,r,t),q;throw'poly.overload: All tests must be functions or strings referencing `is.*`.'},q.list=function(r){return 0<arguments.length?p[r]||[]:p},l.Push(q),h(q,p.default,'default'),q}var l=[],m=Object.prototype.toString;return k.is=function(n){return-1<l.indexOf(n)},k}();
3番目と4番目のポイントから構築された名前の別の関数に進みます。
- 引数の数を使う
- 引数の種類を確認する
window['foo_'+arguments.length+'_'+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments)
function foo(){
return window['foo_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments);
}
//------Assuming that `x` , `y` and `z` are String when calling `foo` .
/**-- for : foo(x)*/
function foo_1_string(){
}
/**-- for : foo(x,y,z) ---*/
function foo_3_string_string_string(){
}
function foo(){
return window['foo_'+arguments.length+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments);
}
/** one argument & this argument is string */
function foo_1_string(){
}
//------------
/** one argument & this argument is object */
function foo_1_object(){
}
//----------
/** two arguments & those arguments are both string */
function foo_2_string_string(){
}
//--------
/** Three arguments & those arguments are : id(number),name(string), callback(function) */
function foo_3_number_string_function(){
let args=arguments;
new Person(args[0],args[1]).onReady(args[3]);
}
//--- And so on ....
最初のオプションは、私が非常に複雑なコード設定で思いついたものなので、本当に注意に値します。だから、私の答えは
- そもそも別の名前を使う
ちょっとした、しかし本質的なヒントで、名前はあなたのためではなく、コンピュータのために異なって見えるべきです。 func、func1、func2のようなオーバーロードされた関数に名前を付けます。
あなたのユースケースのために、これは私がES6
でそれに取り組む方法です(それはすでに2017年の終わりです):
const foo = (x, y, z) => {
if (y && z) {
// Do your foo(x, y, z); functionality
return output;
}
// Do your foo(x); functionality
return output;
}
あなたは明らかにこれを任意の量のパラメータで動作するように適応させることができ、それに応じてあなたの条件文を変更することができます。
JavaScriptは型付けされていない言語であり、パラメータの数に関してメソッド/関数をオーバーロードするのは理にかなっていると私は思います。したがって、パラメータが定義されているかどうかを確認することをお勧めします。
myFunction = function(a, b, c) {
if (b === undefined && c === undefined ){
// do x...
}
else {
// do y...
}
};
2017年7月現在、以下が一般的な手法です。関数内で型チェックを実行することもできます。
function f(...rest){ // rest is an array
console.log(rest.length);
for (v of rest) if (typeof(v)=="number")console.log(v);
}
f(1,2,3); // 3 1 2 3
これは古い質問ですが、私は別のエントリが必要だと思います(だれかがそれを読むことは疑いますが)。即時呼び出し関数式(IIFE)の使用は、関数のオーバーロードを可能にするためにクロージャーおよびインライン関数と組み合わせて使用できます。次の(考案された)例を考えてください。
var foo;
// original 'foo' definition
foo = function(a) {
console.log("a: " + a);
}
// define 'foo' to accept two arguments
foo = (function() {
// store a reference to the previous definition of 'foo'
var old = foo;
// use inline function so that you can refer to it internally
return function newFoo(a,b) {
// check that the arguments.length == the number of arguments
// defined for 'newFoo'
if (arguments.length == newFoo.length) {
console.log("a: " + a);
console.log("b: " + b);
// else if 'old' is a function, apply it to the arguments
} else if (({}).toString.call(old) === '[object Function]') {
old.apply(null, arguments);
}
}
})();
foo(1);
> a: 1
foo(1,2);
> a: 1
> b: 2
foo(1,2,3)
> a: 1
つまり、IIFEを使用するとローカルスコープが作成され、関数old
の初期定義への参照を格納するためのプライベート変数foo
を定義できます。次に、この関数は、2つの引数newFoo
とa
が渡された場合は両方の2つの引数の内容をログに記録するインライン関数b
を返すか、arguments.length !== 2
の場合はold
関数を呼び出します。 1つの変数にいくつかの異なる機能定義を与えるために、このパターンを何回でも繰り返すことができます。
関数のオーバーロードのためにこのようなことができます。
function addCSS(el, prop, val) {
return {
2: function() {
// when two arguments are set
// now prop is an oject
for (var i in prop) {
el.style[i] = prop[i];
}
},
3: function() {
// when three arguments are set
el.style[prop] = val;
}
}[arguments.length]();
}
// usage
var el = document.getElementById("demo");
addCSS(el, "color", "blue");
addCSS(el, {
"backgroundColor": "black",
"padding": "10px"
});
過負荷のようなアプローチの便利な例を共有したいと思います。
function Clear(control)
{
var o = typeof control !== "undefined" ? control : document.body;
var children = o.childNodes;
while (o.childNodes.length > 0)
o.removeChild(o.firstChild);
}
使用法:クリア()。 //すべての文書を消去します
クリア(myDiv)。 // myDivによって参照されているパネルをクリアする
私は何年もの間私の過負荷を分かりやすくするためにこの関数を使ってきました:
function overload(){
const fs = arguments, fallback = fs[fs.length - 1];
return function(){
const f = fs[arguments.length] || (arguments.length >= fs.length ? fallback : null);
return f.apply(this, arguments);
}
}
デモストレーション:
function curry1(f){
return curry2(f, f.length);
}
function curry2(f, minimum){
return function(...applied){
if (applied.length >= minimum) {
return f.apply(this, applied);
} else {
return curry2(function(...args){
return f.apply(this, applied.concat(args));
}, minimum - applied.length);
}
}
}
export const curry = overload(null, curry1, curry2);
JQueryのoff
メソッドを見てください。
function off( types, selector, fn ) {
var handleObj, type;
if ( types && types.preventDefault && types.handleObj ) {
// ( event ) dispatched jQuery.Event
handleObj = types.handleObj;
jQuery( types.delegateTarget ).off(
handleObj.namespace ?
handleObj.origType + "." + handleObj.namespace :
handleObj.origType,
handleObj.selector,
handleObj.handler
);
return this;
}
if ( typeof types === "object" ) {
// ( types-object [, selector] )
for ( type in types ) {
this.off( type, selector, types[ type ] );
}
return this;
}
if ( selector === false || typeof selector === "function" ) {
// ( types [, fn] )
fn = selector;
selector = undefined;
}
if ( fn === false ) {
fn = returnFalse;
}
return this.each( function() {
jQuery.event.remove( this, types, fn, selector );
} );
}
パフォーマンスのために最適化されたときに多くのオーバーロードされた関数はほとんど読めません。あなたは関数の頭を解読しなければなりません。これは私が提供しているようなoverload
関数を使うよりもおそらく速いです。ただし、どの過負荷が呼び出されたかを識別することに関しては、人間の観点からは遅くなります。
jSには実際のオーバーロードはありません。とにかく、いくつかの方法でメソッドのオーバーロードをシミュレートできます。
方法#1: オブジェクトを使う
function test(x,options){
if("a" in options)doSomething();
else if("b" in options)doSomethingElse();
}
test("ok",{a:1});
test("ok",{b:"string"});
方法#2: 残りの(拡散)パラメータを使う
function test(x,...p){
if(p[2])console.log("3 params passed"); //or if(typeof p[2]=="string")
else if (p[1])console.log("2 params passed");
else console.log("1 param passed");
}
方法#3: 未定義を使う
function test(x, y, z){
if(typeof(z)=="undefined")doSomething();
}
方法#4: 型チェック
function test(x){
if(typeof(x)=="string")console.log("a string passed")
else ...
}
この問題を解決するために over.js を作成したのは非常にエレガントな方法です。できるよ:
var obj = {
/**
* Says something in the console.
*
* say(msg) - Says something once.
* say(msg, times) - Says something many times.
*/
say: Over(
function(msg$string){
console.info(msg$string);
},
function(msg$string, times$number){
for (var i = 0; i < times$number; i++) this.say(msg$string);
}
)
};
私は@ AntouanKのアプローチが好きです。私はしばしば自分自身が異なる数のパラメータや異なるタイプの関数を提供しているのに気づきます。時々彼らは命令に従わない。私はパラメータの種類を見てマッピングするために使用します。
findUDPServers: function(socketProperties, success, error) {
var fqnMap = [];
fqnMap['undefined'] = fqnMap['function'] = function(success, error) {
var socketProperties = {name:'HELLO_SERVER'};
this.searchServers(socketProperties, success, error);
};
fqnMap['object'] = function(socketProperties, success, error) {
var _socketProperties = _.merge({name:'HELLO_SERVER'}, socketProperties || {});
this.searchServers(_socketProperties, success, error);
};
fqnMap[typeof arguments[0]].apply(this, arguments);
}
だから私はJavaScriptの忍者の秘密に見つけたことをやるこのやり方が本当に好きだった
function addMethod(object,name,fn){
var old = object[name];
object[name] = function(){
if (fn.length == arguments.length){
return fn.apply(this,arguments);
} else if(typeof old == 'function'){
return old.apply(this,arguments);
}
}
}
その後、addMethodを使用して、オーバーロードされた関数を任意のオブジェクトに追加します。私にとってこのコードの主な混乱は、fn.length == arguments.lengthの使用でした。これは、fn.lengthが予期されるパラメーターの数であるのに対して、arguments.lengthは、実際に呼び出されたパラメーターの数であるために機能します。関数。無名関数に引数がないのは、JavaScriptで引数をいくつでも渡すことができ、言語が寛容だからです。
あなたがそれをどこでも使うことができるので、私はこれが好きでした - ちょうどこの関数を作成して、そしてあなたが望むどんなコードベースででも単にメソッドを使うこと。
また、複雑なコードを書き始めると読みにくくなるという、途方もなく大きいif/switchステートメントを使用することも避けられます(承認された答えはこの結果になります)。
短所に関しては、私はコードが最初は少しあいまいであると思います...しかし、私は他の人のことを確信していませんか?
関数のオーバーロードとは、異なる実装で同じ名前の複数の関数を作成するプログラミング言語の機能です。オーバーロードされた関数が呼び出されると、呼び出しのコンテキストに適した関数の特定の実装を実行します。このコンテキストは通常、受け取る引数の量であり、1つの関数呼び出しがコンテキストに応じて異なる動作をすることを可能にします。
Javascript does n'tには組み込みの関数オーバーロードがあります。ただし、この動作はさまざまな方法でエミュレートできます。これは便利でシンプルなものです:
function sayHi(a, b) {
console.log('hi there ' + a);
if (b) { console.log('and ' + b) } // if the parameter is present, execute the block
}
sayHi('Frank', 'Willem');
取得する引数の数がわからないシナリオでは、3つのドット...
であるrest operatorを使用できます。残りの引数を配列に変換します。ただし、ブラウザの互換性に注意してください。以下に例を示します。
function foo (a, ...b) {
console.log(b);
}
foo(1,2,3,4);
foo(1,2);