JavaScriptでの関数のオーバーロード-ベストプラクティス


783

JavaScriptで関数のオーバーロードを偽造する最良の方法は何ですか?

他の言語のようにJavaScriptで関数をオーバーロードすることはできないことを知っています。私が2つの用途foo(x)を持つ関数を必要としてfoo(x,y,z)いて、それが最善の/好ましい方法である場合:

  1. 最初に異なる名前を使用する
  2. のようなオプションの引数を使用する y = y || 'default'
  3. 引数の数を使用する
  4. 引数のタイプを確認する
  5. またはどうやって?

14
おそらく、最初に関数のオーバーロードが必要だと思う理由を尋ねると役立つでしょう。これにより、実際のソリューションに近づくことができると思います。
ブルトン語

1
これは閉じていますが、私は次のことを行います。this.selectBy = {instance:selectByInstance、// Function text:selectByText、// Function value:selectByValue // Function};
囚人ZERO

私の答えは、実行時関数のオーバーロードを行う方法を示しています。これには速度のペナルティがあり、Javascriptの仕様を回避するために実行することはお勧めしません。関数のオーバーロードは、実際にはコンパイル時のタスクです。私は、学術目的でのみ答えを提供し、それをコードで使用するかどうかについては、あなた自身の裁量に任せます。
Keldon Alleyne、2012年

2
役に立つ場合に備えて、型ベースのメソッドのオーバーロードを可能にする軽量のjsフレームワークを作成しました。明らかに、パフォーマンスに関しては同じ警告が当てはまりますが、これまでのところ私のニーズにはうまく機能しており、まだ改善の余地がかなりあります: blog.pebbl.co.uk/2013/01/describejs.html#methodoverloading
Pebbl

回答:


602

パラメータを使用して関数のオーバーロードを行う最善の方法は、引数の長さや型をチェックしないことです。型をチェックすると、コードが遅くなるだけで、配列、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"});

次に、メソッドで必要に応じてそれを処理できます。[スイッチ、if-elseなど]


43
これらの「opts」パラメーターがどのように使用/参照されるかを示すfoo()のサンプル実装を提供できますか?
モーハワード

24
萌//こんな感じです。if(opts['test']) //if test param exists, do something.. if(opts['bar']) //if bar param exists, do something
Deckard

80
これは関数のオーバーロードではありません。関数のオーバーロードとは、名前が同じでパラメーターが異なる2つの別個の関数のことです。あなたが説明しているのは、最後にオブジェクト引数を持つ1つの関数だけです。
d512

66
@ user1334007 Java / .NETで行うように関数をオーバーロードすることは不可能です。はい、これは「正確に」過負荷ではありませんが、仕事をします。
epascarello 2013年

18
誰もこれをすでに求めていないことに驚いています:なぜチェックしているのですか arguments.lengthが推奨されないのですか?また、私は前にここにされていると読んだほとんどの開発者は何をすべきか...ですが、私は確かにこれは私が行っていることを見てきた唯一の場所ですよ。その方法はまた、「オーバーロード」を持つ構文の甘さを台無しにします!
c24w 2013

169

私はよくこれをします:

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

この特定の例は、実際にはJavaScriptでC#よりもエレガントです。指定されていないパラメーターはJavaScriptでは「未定義」であり、ifステートメントでfalseと評価されます。ただし、関数定義は、p2とp3がオプションであるという情報を伝えません。大量のオーバーロードが必要な場合、jQueryはオブジェクトをパラメーターとして使用することを決定しました(例:jQuery.ajax(options))。これは、オーバーロードに対する最も強力で明確に文書化可能なアプローチであることに同意しますが、1つまたは2つ以上のクイックオプションパラメーターが必要になることはほとんどありません。

編集:イアンの提案に従ってIFテストを変更


16
指定されていないパラメーターundefinedはJSにあり、ではありませんnull。ベストプラクティスとして、何もに設定しundefinedないでくださいp2 === undefined。テストをに変更する限り、問題にはなりません。
Tamzin Blake

3
false最後の引数として渡すと、ブランチが行われない"false"ため、最後に連結されif(p3)ません。
dreamlax 2012年

5
簡単に言えtypeof p2 === "undefined"ば、あなたの例のインスタンスであなたが期待しているもののおそらく逆です。私typeof p2 !== "undefined"はあなたが意図したものだと思います。また、あなたが実際に行う文字列、数値、ブール値を連結することをお勧めしますp2 === "number"; p3 === "boolean"
WillFM

8
私はこれをするのが好きです:p3 = p3 || 'デフォルト値';
ドリアン

3
===and の意味は何!==ですか?なぜ使用==しないの!=ですか?
Ricardo Cruz

76

JavaScriptでは、任意のタイプの任意の数のパラメーターを渡すことができるため、実際の関数のオーバーロードはありません。関数内で渡された引数の数と引数のタイプを確認する必要があります。


1
(jQueryの)John Resigはかつてこれを試しましたが、この試みは純粋に学術的なものであり、実際のメリットはありませんでした。
scunliffe 2009年

14
John Resigの関数オーバーロードはこちらejohn.org/blog/javascript-method-overloading
Terrance

@Terrance:Resigの方法も好きです。それは魅力のように働きます。ユースケースを検証するためのテストを作成する方法を見つける必要があるだけです。
chrisvillanueva 2013年

「この関数は世界を変えることはありませんが、短くて簡潔であり、あいまいなJavaScript機能を使用しているため、私の本で勝っています。」:-)
Frerich Raabe

67

正解は、JAVASCRIPTにはオーバーロードがありません。

関数内のチェック/切り替えはオーバーロードではありません。

オーバーロードの概念:一部のプログラミング言語では、関数のオーバーロードまたはメソッドのオーバーロードは、異なる実装で同じ名前の複数のメソッドを作成する機能です。オーバーロードされた関数の呼び出しは、呼び出しのコンテキストに適したその関数の特定の実装を実行し、1つの関数呼び出しがコンテキストに応じて異なるタスクを実行できるようにします。

たとえば、doTask()およびdoTask(object O)はオーバーロードされたメソッドです。後者を呼び出すには、オブジェクトをパラメーターとして渡す必要がありますが、前者はパラメーターを必要とせず、空のパラメーターフィールドを指定して呼び出されます。一般的なエラーは、2番目のメソッドでオブジェクトにデフォルト値を割り当てることです。これは、コンパイラーが2つのメソッドのどちらを使用するかを認識できないため、あいまいな呼び出しエラーを引き起こします。

https://en.wikipedia.org/wiki/Function_overloading

提案された実装はすべてすばらしいですが、JavaScriptのネイティブ実装はありません。


4
ついに通常の答え!JAVASCRIPTにはオーバーロードはありません。
Razvan Tudorica

4
OPは、オーバーロードを偽造する方法を求めました。
Ateur Games

私が以前言ったように、私たちはここで人々を教育します、私たちは彼らが求めているものが正しいことを確認することなく彼らに答えを与えるだけではありません。
Marco

27

これをより効果的に行うには、次の2つの方法があります。

  1. 柔軟性を残したい場合は、辞書(連想配列)を渡します。

  2. 引数としてオブジェクトを取り、プロトタイプベースの継承を使用して柔軟性を追加します。


1
これは私の最初の考えでしたが、作成している関数がライブラリまたは他のユーザーによって使用される場合、値を明白に列挙することが役立つ場合があります
roberthuttinger

19

次に示すのは、パラメータタイプを使用して実際のメソッドのオーバーロードを可能にするアプローチです。

Func(new Point());
Func(new Dimension());
Func(new Dimension(), new Point());
Func(0, 0, 0, 0);

編集(2018):これは2011年に作成されて以来、直接的なメソッド呼び出しの速度は大幅に向上していますが、オーバーロードされたメソッドの速度はそうではありません。

これは私がお勧めする方法ではありませんが、これらの種類の問題をどのように解決できるかを考えることは価値のあることです。


これは、さまざまなアプローチのベンチマークです-https://jsperf.com/function-overloading。これは、関数のオーバーロード(タイプを考慮に入れる)がGoogle ChromeのV816.0(beta)の時点で約13倍遅くなる可能性があることを示しています。

オブジェクトを渡す(つまり{x: 0, y: 0})だけでなく、適切な場合はCのアプローチを使用して、それに応じてメソッドに名前を付けることもできます。たとえば、Vector.AddVector(vector)、Vector.AddIntegers(x、y、z、...)およびVector.AddArray(integerArray)。OpenGLなどのCライブラリを調べて、インスピレーションに名前を付けることができます。

編集:私は、オブジェクトを渡すとの両方を使用してオブジェクトをテストするためのベンチマークを追加しました'param' in argし、arg.hasOwnProperty('param')(少なくともこのベンチマークでは)はるかに高速にオブジェクトを渡すとプロパティをチェックするよりも、および関数のオーバーロードがあります。

設計の観点から見ると、関数のオーバーロードは、オーバーロードされたパラメーターが同じアクションに対応している場合にのみ有効または論理的です。したがって、特定の詳細のみに関係する基礎となる方法があるべきであるという理由は当然です。そうでなければ、不適切な設計の選択を示す可能性があります。したがって、データをそれぞれのオブジェクトに変換することにより、関数のオーバーロードの使用を解決することもできます。もちろん、名前を印刷するだけなら手の込んだデザインを作成する必要はないが、フレームワークやライブラリのデザインについてはそのような考えは正当化されるため、問題の範囲を考慮する必要があります。

私の例は、Rectangleの実装から来ています-したがって、DimensionとPointについて言及しています。おそらく、RectangleがとプロトタイプにGetRectangle()メソッドを追加し、関数のオーバーロードの問題がソートされます。そして、プリミティブはどうですか?さて、引数の長さがあります。オブジェクトにはメソッドがあるので、これは現在有効なテストです。DimensionPointGetRectangle()

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);

16

最良の方法は、実際には関数と引数によって異なります。それぞれのオプションは、さまざまな状況で適切です。私は通常、次の順序でこれらのいずれかが機能するまで試します。

  1. y = y ||のようなオプションの引数を使用する 'デフォルト'。これはできれば便利ですが、たとえば0 / null / undefinedが有効な引数である場合など、実際に機能するとは限りません。

  2. 引数の数を使用します。最後のオプションに似ていますが、#1が機能しないときに機能する可能性があります。

  3. 引数のタイプをチェックしています。これは、引数の数が同じ場合に機能します。タイプを確実に判別できない場合は、別の名前を使用する必要がある場合があります。

  4. 最初に別の名前を使用する。他のオプションが機能しない場合、実用的でない場合、または他の関連機能との整合性のために、これを行う必要がある場合があります。


14

2つの関数を使用する必要がある場合は、foo(x)とfoo(x、y、z)が最適です。

問題は、JavaScriptがメソッドのオーバーロードをネイティブでサポートしていないことです。したがって、同じ名前の2つ以上の関数を検出または解析する場合は、最後に定義された関数を考慮し、以前の関数を上書きします。

私がほとんどの場合に適していると思う方法の1つは次のとおりです-

メソッドがあるとしましょう

function foo(x)
{
} 

JavaScriptでは不可能なメソッドオーバーロードする代わりに、新しいメソッドを定義できます

fooNew(x,y,z)
{
}

次に、1番目の関数を次のように変更します-

function foo(arguments)
{
  if(arguments.length==2)
  {
     return fooNew(arguments[0],  arguments[1]);
  }
} 

このようなオーバーロードされたメソッドが多数ある場合は、ステートメントswitchだけではなく使用を検討してif-elseください。

詳細

PS:上記のリンクは、詳細が記載されている私の個人ブログへのリンクです。


9

ベストプラクティスについてはわかりませんが、ここではその方法を示します。

/*
 * 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"

2
@Luis:役立つと思われるコメントをいくつか追加しました。
chessweb 2015

6

私はこれを試してみましたが、おそらくあなたのニーズに合っています。引数の数に応じて、別の関数にアクセスできます。初めて呼び出すときに初期化します。そして、関数マップはクロージャーに隠されています。

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");

5

JavaScriptには関数オーバーロードオプションがないため、代わりにオブジェクトを使用できます。必要な引数が1つまたは2つある場合は、オプションオブジェクトとは別にしておくことをお勧めします。オプションオブジェクトで値が渡されなかった場合に備えて、オプションオブジェクトと入力された値をデフォルト値に使用する方法の例を次に示します。

    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 + "}";

}

これはオプションオブジェクトの使用方法の例です


4

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!!");
     }
}

幸運を!


24
たのむよ!switchステートメントを使用してください!
jedmao 2013年

8
また、スイッチを使用しない場合は、typeofを1回だけ呼び出す必要があります。var type = typeof param; if (type === 'string') ...
Walter Stabosz 2013

「===」にコメントするには+1。if(... == ...)に対するswitchステートメントのもう1つの利点は、タイプセーフであることです。
Nathan Cooper

4

前書き

これまでのところ、非常に多くの答えを読むことは誰にも頭痛の種になるでしょう。概念を知りたい人は、次の前提条件を知っている必要があります。

Function overloading DefinitionFunction Length propertyFunction 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

-JSは、組み込みの関数オーバーロード機能を提供していません。

オルタナティブ

John E Resig(JSの作成者)は、上記の前提条件を使用して関数のオーバーロードを実装する機能を実現する代替方法を指摘しています。

以下のコードは、if-elseor 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、3つの関数を追加しninja.whatever(x)ます。xこれらは、任意の数の引数(つまり、空白または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);


4

これに取り組む別の方法は、特別な変数を使用することです: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;
}

3

これをチェックしてください。とてもかっこいいです。 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

こんにちはJaider、私の答えを確認してください。実際の JavaScriptメソッドのオーバーロードのコードが含まれています。私は話していてFunc(new Point())Func(new Rectangle())さまざまな機能を実行します。しかし、メソッドのオーバーロードは実際には実行時ではなくコンパイル時のタスクであるため、これは汚いハックであることを指摘しなければなりません。
Keldon Alleyne、2012年

3

JavaScriptでの関数のオーバーロード:

関数のオーバーロードとは、異なる実装で同じ名前の複数の関数を作成するプログラミング言語の機能です。オーバーロードされた関数が呼び出されると、関数は、呼び出しのコンテキストに適したその関数の特定の実装を実行します。このコンテキストは通常​​、受け取る引数の量であり、1つの関数呼び出しがコンテキストに応じて異なる動作をすることを可能にします。

JavaScriptには、組み込み関数のオーバーロードはありません。ただし、この動作はさまざまな方法でエミュレートできます。ここに便利なシンプルなものがあります:

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つのドットである残りの演算子を使用できます...。残りの引数を配列に変換します。ただし、ブラウザの互換性に注意してください。次に例を示します。

function foo (a, ...b) {
  console.log(b);
}

foo(1,2,3,4);
foo(1,2);


2

この投稿にはすでに多くの異なるソリューションが含まれているので、別のソリューションを投稿すると思いました。

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));

このソリューションは完璧ではありませんが、私はそれがどのように実行できるかを示すだけです。


2

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);
  };
}

2

転送パターン=> JS過負荷のベストプラクティス

名前が3番目と4番目のポイントから作成された別の関数に転送します。

  1. 引数の数を使用する
  2. 引数のタイプを確認する
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 ....   

2

100行のJSでの動的ポリモーフィズムによる関数のオーバーロード

これは、コードのより大きな本体からなるisFnisArrなど型チェック機能を。以下のVanillaJSバージョンは、すべての外部依存関係を削除するように再加工されていますが、.add()呼び出しで使用するには、独自の型チェック関数を定義する必要があります。

注:これは自己実行関数(つまり、クロージャー/クローズスコープを持つことができる)なので、window.overloadではなくへの割り当てですfunction 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()は次のとおりです。

  1. fnfunctionオーバーロードの定義;
  2. a_vArgumentTestsArrayfunction上で実行するテストを定義するのarguments。それぞれfunctionが単一の引数を受け入れtrue、引数が有効かどうかに基づいてthyを返します。
  3. sAlias(オプション):stringオーバーロード関数(fn)に直接アクセスするためのエイリアスを定義します。たとえば、myOverloadedFn.noArgs()その関数を直接呼び出し、引数の動的ポリモーフィズムテストを回避します。

この実装では、実際には2番目のa_vArgumentTests引数が.add()カスタムタイプを定義するため、実際には従来の関数オーバーロードだけではありません。したがって、タイプだけでなく、範囲、値、または値のコレクションに基づいて引数をゲートすることができます!

145行のコードをoverload()確認すると、各署名がarguments渡された数によって分類されていることがわかります。これは、実行するテストの数を制限するために行われます。また、コール数も追跡します。いくつかの追加のコードを使用すると、オーバーロードされた関数の配列を再ソートして、より一般的に呼び出される関数を最初にテストして、パフォーマンスをある程度向上させることができます。

さて、いくつかの注意点があります...のようJavascriptが緩く型付けされ、あなたと注意する必要がありますvArgumentTestsようintegerとして検証することができfloat、など

JSCompress.comバージョン(1114バイト、744バイトg-zip):

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}();

2

ECMAScript 2018で、ポリフィルなし、varの長さ/タイプなどをチェックせずに関数のオーバーロードを実行できるようになりました。単に、spread構文を使用してください。

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提案のRest / Spreadプロパティ(ステージ4)は、オブジェクトリテラルにSpreadプロパティを追加します。提供されたオブジェクトから新しいオブジェクトに独自の列挙可能なプロパティをコピーします。 MDNの詳細

注:オブジェクトリテラルのスプレッド構文はEdgeとIEでは機能せず、実験的な機能です。ブラウザの互換性を確認する


2

このような何かは、関数のオーバーロードのために行うことができます。

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"
});

ソース


1

これは古い質問ですが、別のエントリが必要だと思います(だれもそれを読むとは思いませんが)。即時に呼び出される関数式(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。この関数は、インライン関数を返しnewFoo、それが正確に二つの引数を渡された場合、両方の二つの引数の内容をログに記録aし、bまたは呼び出したold場合、関数をarguments.length !== 2。このパターンは何度でも繰り返すことができ、1つの変数に複数の異なる機能定義を与えることができます。


1

オーバーロードのようなアプローチの有用な例を共有したいと思います。

function Clear(control)
{
  var o = typeof control !== "undefined" ? control : document.body;
  var children = o.childNodes;
  while (o.childNodes.length > 0)
    o.removeChild(o.firstChild);
}

使用法:Clear(); //すべてのドキュメントをクリアします

Clear(myDiv); // myDivによって参照されるパネルをクリアします


1

JavaScriptは型なし言語であり、パラメーターの数に関してメソッド/関数をオーバーロードすることは理にかなっていると思います。したがって、パラメーターが定義されているかどうかを確認することをお勧めします。

myFunction = function(a, b, c) {
     if (b === undefined && c === undefined ){
          // do x...
     }
     else {
          // do y...
     }
};

1
型なしは「型なし」を意味しないことに注意してください。
hamdiakoguz

1

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

1

あなたのユースケースでは、これは私がそれに取り組む方法です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;
}

これを明らかに調整して、任意の量のパラメーターで機能し、それに応じて条件ステートメントを変更することができます。


1

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 ...
}

1

一方でデフォルトのパラメータがオーバーロードしていない、それは、開発者がこの領域で直面する問題のいくつかを解決する可能性があります。入力は厳密に順序によって決定され、従来のオーバーロードのように、希望どおりに並べ替えることはできません。

function transformer(
    firstNumber = 1,
    secondNumber = new Date().getFullYear(),
    transform = function multiply(firstNumber, secondNumber) {
        return firstNumber * secondNumber;
    }
) {
    return transform(firstNumber, secondNumber);
}

console.info(transformer());
console.info(transformer(8));
console.info(transformer(2, 6));
console.info(transformer(undefined, 65));

function add(firstNumber, secondNumber) {
    return firstNumber + secondNumber;
}
console.info(transformer(undefined, undefined, add));
console.info(transformer(3, undefined, add));

(2020年の)結果:

2020
16160
12
65
2021
2023

詳細:https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters


0

最初のオプションは本当に注目に値します。なぜなら、それが私が非常に複雑なコード設定で思いついたことだからです。だから、私の答えは

  1. 最初に異なる名前を使用する

少しではありますが重要なヒントを使用すると、コンピューターでは名前が異なって見えるはずですが、あなたにとっては違います。func、func1、func2などのオーバーロードされた関数に名前を付けます。


私はオーバーロードを試すつもりでしたが、たとえばgetDeviceInfoByIDとgetDeviceInfoByType ...などの異なる名前を使用することにしました...
CramerTV
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.