Funktionsüberladung in Javascript - Best Practices

783

Was ist der beste Weg, um das Überladen von Funktionen in Javascript vorzutäuschen?

Ich weiß, dass es nicht möglich ist, Funktionen in Javascript wie in anderen Sprachen zu überladen. Wenn ich eine Funktion mit zwei Verwendungszwecken brauchte foo(x)und foo(x,y,z)welche der beste / bevorzugte Weg ist:

  1. Verwenden Sie zunächst verschiedene Namen
  2. Mit optionalen Argumenten wie y = y || 'default'
  3. Anzahl der Argumente verwenden
  4. Überprüfung der Argumenttypen
  5. Oder wie?
hamdiakoguz
quelle
14
Vielleicht wäre es nützlich zu fragen, warum Sie denken, dass Sie zunächst eine Funktionsüberladung benötigen. Ich denke, das bringt uns einer echten Lösung näher.
Breton
1
Dies ist geschlossen, aber ich mache Folgendes: this.selectBy = {Instanz: selectByInstance, // Funktionstext: selectByText, // Funktionswert: selectByValue // Funktion};
Gefangener
Meine Antwort zeigt, wie man Laufzeitfunktionen überlastet, es hat eine Geschwindigkeitsstrafe und ich würde nicht empfehlen, dies zu tun, um die Javascript-Spezifikation zu umgehen. Das Überladen von Funktionen ist wirklich eine Aufgabe zur Kompilierungszeit. Ich gebe die Antwort nur für akademische Zwecke und überlasse es Ihrem eigenen Ermessen, ob Sie sie im Code verwenden möchten oder nicht.
Keldon Alleyne
2
Für den Fall, dass es nützlich ist, habe ich ein leichtes js-Framework erstellt, das eine typbasierte Methodenüberladung ermöglicht. Natürlich gelten die gleichen Einschränkungen in Bezug auf die Leistung, aber es hat bisher gut für meine Bedürfnisse funktioniert und bietet noch viel Raum für Verbesserungen: blog.pebbl.co.uk/2013/01/describejs.html#methodoverloading
Pebbl

Antworten:

602

Der beste Weg, um eine Funktionsüberladung mit Parametern durchzuführen, besteht darin, die Argumentlänge oder die Typen nicht zu überprüfen. Wenn Sie die Typen überprüfen, wird Ihr Code nur langsam und Sie haben Spaß an Arrays, Nullen, Objekten usw.

Die meisten Entwickler greifen ein Objekt als letztes Argument für ihre Methoden an. Dieses Objekt kann alles aufnehmen.

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

Dann können Sie damit umgehen, wie Sie es in Ihrer Methode wollen. [Switch, if-else usw.]

Epascarello
quelle
43
Könnten Sie eine Beispielimplementierung von foo () bereitstellen, die veranschaulicht, wie diese "opts" -Parameter verwendet / referenziert werden?
Moe Howard
24
Moe // Es könnte so sein; if(opts['test']) //if test param exists, do something.. if(opts['bar']) //if bar param exists, do something
Deckard
80
Dies ist keine Funktionsüberladung. Die Funktionsüberladung besteht aus zwei separaten Funktionen mit demselben Namen, aber unterschiedlichen Parametern. Was Sie beschreiben, ist nur eine Funktion mit einem Objektargument am Ende.
d512
66
@ user1334007 Es ist unmöglich, dass Funktionen überladen werden, wie Sie es in Java / .NET tun würden. Ja, das ist keine "exakte" Überladung, aber es macht den Job.
Epascarello
18
Ich bin überrascht, dass dies noch niemand gefragt hat: Warum wird eine Überprüfung arguments.lengthnicht empfohlen? Außerdem war ich schon einmal hier und habe gelesen, was die meisten Entwickler tun, ist ... aber ich bin sicher, dass dies der einzige Ort ist, an dem ich das gesehen habe. Diese Methode verdirbt auch die syntaktische Zuckerhaftigkeit von "Überladungen"!
c24w
169

Ich mache das oft:

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-Äquivalent:

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

Dieses spezielle Beispiel ist in Javascript tatsächlich eleganter als C #. Parameter, die nicht angegeben werden, sind in Javascript 'undefiniert', was in einer if-Anweisung als false ausgewertet wird. Die Funktionsdefinition vermittelt jedoch nicht die Information, dass p2 und p3 optional sind. Wenn Sie viel überladen müssen, hat jQuery beschlossen, ein Objekt als Parameter zu verwenden, z. B. jQuery.ajax (Optionen). Ich stimme ihnen zu, dass dies der leistungsstärkste und klar dokumentierbarste Ansatz zur Überladung ist, aber ich benötige selten mehr als ein oder zwei schnelle optionale Parameter.

BEARBEITEN: IF-Test gemäß Ians Vorschlag geändert

Raketen sind schnell
quelle
16
Parameter, die nicht angegeben sind undefined, sind in JS, nicht null. Als bewährte Methode sollten Sie niemals etwas festlegen undefined, daher sollte dies kein Problem sein, solange Sie Ihren Test auf ändern p2 === undefined.
Tamzin Blake
3
Wenn Sie falseals letztes Argument übergeben, wird es nicht "false"bis zum Ende verkettet , da das if(p3)nicht verzweigt wird.
Dreamlax
5
Nur eine kurze Anmerkung, Sie typeof p2 === "undefined"sind wahrscheinlich das Gegenteil von dem, was Sie in Ihrem Beispiel erwarten. Ich denke, es typeof p2 !== "undefined"ist das, was Sie beabsichtigt haben. Darf ich auch vorschlagen, dass es Zeichenfolge, Nummer und Booleschen p2 === "number"; p3 === "boolean"
Wert
8
Ich mache das gerne: p3 = p3 || 'Standardwert';
Dorian
3
Was bedeutet ===und !==? Warum nicht einfach ==und verwenden !=?
Ricardo Cruz
76

In JavaScript gibt es keine echte Funktionsüberladung, da eine beliebige Anzahl von Parametern eines beliebigen Typs übergeben werden kann. Sie müssen innerhalb der Funktion überprüfen, wie viele Argumente übergeben wurden und welchen Typ sie haben.

Gumbo
quelle
1
John Resig (von jQuery) hat dies einmal versucht, aber der Versuch war rein akademisch und brachte keinen wirklichen Nutzen.
Scunliffe
14
John Resigs
Terrance
@Terrance: Ich mag auch Resigs Methode. Es wirkt wie ein Zauber. Ich muss nur einen Weg finden, einen Test zu erstellen, um Anwendungsfälle zu validieren.
Chrisvillanueva
"Diese Funktion wird die Welt nicht verändern, aber sie ist kurz, prägnant und verwendet eine obskure JavaScript-Funktion - also gewinnt sie in meinem Buch." :-)
Frerich Raabe
67

Die richtige Antwort lautet: ES GIBT KEINE ÜBERLASTUNG IN JAVASCRIPT.

Das Überprüfen / Schalten innerhalb der Funktion ist nicht ÜBERLASTET.

Das Konzept des Überladens: In einigen Programmiersprachen ist das Überladen von Funktionen oder das Überladen von Methoden die Möglichkeit, mehrere gleichnamige Methoden mit unterschiedlichen Implementierungen zu erstellen. Aufrufe einer überladenen Funktion führen eine bestimmte Implementierung dieser Funktion aus, die dem Kontext des Aufrufs entspricht, sodass ein Funktionsaufruf je nach Kontext unterschiedliche Aufgaben ausführen kann.

Beispielsweise sind doTask () und doTask (Objekt O) überladene Methoden. Um Letzteres aufzurufen, muss ein Objekt als Parameter übergeben werden, während Ersteres keinen Parameter benötigt und mit einem leeren Parameterfeld aufgerufen wird. Ein häufiger Fehler besteht darin, dem Objekt in der zweiten Methode einen Standardwert zuzuweisen, was zu einem mehrdeutigen Aufruffehler führen würde, da der Compiler nicht weiß, welche der beiden Methoden verwendet werden soll.

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

Alle vorgeschlagenen Implementierungen sind großartig, aber um ehrlich zu sein, gibt es keine native Implementierung für JavaScript.

Marco
quelle
4
endlich eine normale antwort! ES GIBT KEINE ÜBERLASTUNG IN JAVASCRIPT.
Razvan Tudorica
4
OP bat um einen Weg zu fälschen Überlastung.
Ateur Games
Wie ich bereits sagte, sind wir hier, um Menschen zu erziehen, wir geben ihnen nicht nur Antworten, ohne zu überprüfen, ob das, was sie fragen, richtig ist.
Marco
27

Es gibt zwei Möglichkeiten, wie Sie dies besser angehen können:

  1. Übergeben Sie ein Wörterbuch (assoziatives Array), wenn Sie viel Flexibilität lassen möchten

  2. Nehmen Sie ein Objekt als Argument und verwenden Sie die prototypbasierte Vererbung, um die Flexibilität zu erhöhen.

t3rse
quelle
1
Dies war jedoch mein erster Gedanke, wenn die von Ihnen erstellte Funktion in einer Bibliothek oder von anderen verwendet werden soll, kann es hilfreich sein, die Werte einfach
aufzuzählen
19

Hier ist ein Ansatz, der eine echte Methodenüberladung mithilfe von Parametertypen ermöglicht (siehe unten):

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

Bearbeiten (2018) : Seit dies im Jahr 2011 geschrieben wurde, hat die Geschwindigkeit direkter Methodenaufrufe stark zugenommen, während die Geschwindigkeit überladener Methoden dies nicht getan hat.

Es ist kein Ansatz, den ich empfehlen würde, aber es ist eine lohnende Gedankenübung, darüber nachzudenken, wie Sie diese Art von Problemen lösen können.


Hier ist ein Benchmark der verschiedenen Ansätze - https://jsperf.com/function-overloading . Es zeigt, dass die Funktionsüberladung (unter Berücksichtigung von Typen) in Google Chrome V8 ab 16.0 (Beta) etwa 13-mal langsamer sein kann .

Neben der Übergabe eines Objekts (dh {x: 0, y: 0}) kann bei Bedarf auch der C-Ansatz gewählt werden, wobei die Methoden entsprechend benannt werden. Zum Beispiel Vector.AddVector (Vektor), Vector.AddIntegers (x, y, z, ...) und Vector.AddArray (integerArray). Sie können sich C-Bibliotheken wie OpenGL ansehen, um Inspiration für die Benennung zu erhalten.

Bearbeiten : Ich habe einen Benchmark zum Übergeben eines Objekts und zum Testen des Objekts mit beiden 'param' in argund hinzugefügt arg.hasOwnProperty('param'), und das Überladen von Funktionen ist viel schneller als das Übergeben eines Objekts und das Überprüfen auf Eigenschaften (zumindest in diesem Benchmark).

Aus Entwurfssicht ist die Funktionsüberladung nur dann gültig oder logisch, wenn die überladenen Parameter derselben Aktion entsprechen. Es liegt also nahe, dass es eine zugrunde liegende Methode geben sollte, die sich nur mit bestimmten Details befasst, andernfalls kann dies auf unangemessene Entwurfsentscheidungen hinweisen. Man könnte also auch die Verwendung von Funktionsüberladung auflösen, indem man Daten in ein entsprechendes Objekt konvertiert. Natürlich muss man den Umfang des Problems berücksichtigen, da keine aufwändigen Entwürfe erforderlich sind, wenn Sie nur einen Namen drucken möchten, aber für den Entwurf von Frameworks und Bibliotheken sind solche Überlegungen gerechtfertigt.

Mein Beispiel stammt aus einer Rechteck-Implementierung - daher die Erwähnung von Dimension und Punkt. Vielleicht ein Rechteck könnte hinzufügen GetRectangle()Methode der Dimensionund PointPrototyp, und dann die Funktion Ausgabe Überlastung wird sortiert. Und was ist mit Primitiven? Nun, wir haben eine Argumentlänge, die jetzt ein gültiger Test ist, da Objekte eine GetRectangle()Methode haben.

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);
Keldon Alleyne
quelle
16

Der beste Weg hängt wirklich von der Funktion und den Argumenten ab. Jede Ihrer Optionen ist in verschiedenen Situationen eine gute Idee. Ich versuche diese im Allgemeinen in der folgenden Reihenfolge, bis einer von ihnen funktioniert:

  1. Verwenden optionaler Argumente wie y = y || 'Standard'. Dies ist praktisch, wenn Sie dies tun können, funktioniert jedoch möglicherweise nicht immer praktisch, z. B. wenn 0 / null / undefined ein gültiges Argument wäre.

  2. Anzahl der Argumente verwenden. Ähnlich wie die letzte Option, funktioniert jedoch möglicherweise, wenn # 1 nicht funktioniert.

  3. Überprüfung der Argumenttypen. Dies kann in einigen Fällen funktionieren, in denen die Anzahl der Argumente gleich ist. Wenn Sie die Typen nicht zuverlässig bestimmen können, müssen Sie möglicherweise andere Namen verwenden.

  4. Verwenden Sie zunächst verschiedene Namen. Möglicherweise müssen Sie dies tun, wenn die anderen Optionen nicht funktionieren, nicht praktikabel sind oder mit anderen verwandten Funktionen übereinstimmen.

Matthew Crumley
quelle
14

Wenn ich eine Funktion mit zwei Verwendungszwecken brauchte, foo (x) und foo (x, y, z), welcher ist der beste / bevorzugte Weg?

Das Problem ist, dass JavaScript das Überladen von Methoden NICHT nativ unterstützt. Wenn also zwei oder mehr Funktionen mit demselben Namen angezeigt / analysiert werden, wird nur die zuletzt definierte Funktion berücksichtigt und die vorherigen überschrieben.

Eine der Möglichkeiten, die meiner Meinung nach für den größten Teil des Falls geeignet sind, ist folgende:

Nehmen wir an, Sie haben eine Methode

function foo(x)
{
} 

Anstatt eine Methode zu überladen, die in Javascript nicht möglich ist, können Sie eine neue Methode definieren

fooNew(x,y,z)
{
}

und ändern Sie dann die 1. Funktion wie folgt -

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

Wenn Sie viele solcher überladenen Methoden haben, sollten Sie switchnicht nur if-elseAnweisungen verwenden.

( mehr Details )

PS: Der obige Link führt zu meinem persönlichen Blog, der zusätzliche Details enthält.

Aniket Thakur
quelle
9

Ich bin mir über Best Practices nicht sicher, aber hier ist, wie ich es mache:

/*
 * 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"
Schachweb
quelle
2
@ Luis: Ich habe einige hoffentlich hilfreiche Kommentare hinzugefügt.
Schachweb
6

Ich habe es gerade versucht, vielleicht passt es zu Ihren Bedürfnissen. Abhängig von der Anzahl der Argumente können Sie auf eine andere Funktion zugreifen. Sie initialisieren es beim ersten Aufruf. Und die Funktionskarte ist im Verschluss versteckt.

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

Probier es aus.

TEST.multiFn("0")
    .multiFn()
    .multiFn("0","1","2");
AntouanK
quelle
5

Da JavaScript keine Funktionsüberladungsoptionen hat, kann stattdessen ein Objekt verwendet werden. Wenn ein oder zwei Argumente erforderlich sind, ist es besser, sie vom Optionsobjekt zu trennen. Hier ist ein Beispiel für die Verwendung des Optionsobjekts und der aufgefüllten Werte als Standardwert, falls im Optionsobjekt kein Wert übergeben wurde.

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

}

Hier ist ein Beispiel für die Verwendung des Optionsobjekts

Vlad Bezden
quelle
4

Es gibt keine Möglichkeit, eine Überladung in Javascript zu funktionieren. Daher empfehle ich die folgende typeof()Methode anstelle der Mehrfachfunktion, um eine Überladung vorzutäuschen.

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

Viel Glück!

Smith Lee
quelle
24
Um Himmels Willen! Verwenden Sie eine switch-Anweisung!
jedmao
8
Wenn Sie darauf bestehen, keinen Switch zu verwenden, sollten Sie typeof nur einmal aufrufen. var type = typeof param; if (type === 'string') ...
Walter Stabosz
+1 für das "===" zu kommentieren. Der andere Vorteil der switch-Anweisung gegenüber if (... == ...) ist die Typensicherheit.
Nathan Cooper
4

EINFÜHRUNG

Bisher würde das Durchlesen so vieler Antworten jedem Kopfschmerzen bereiten. Jeder, der versucht, das Konzept zu kennen, muss die folgenden Voraussetzungen kennen .

Function overloading Definition, Function Length property,Function argument property

Function overloadingIn seiner einfachsten Form bedeutet dies, dass eine Funktion verschiedene Aufgaben basierend auf der Anzahl der Argumente ausführt, die an sie übergeben werden. Insbesondere sind TASK1, TASK2 und TASK3 unten hervorgehoben und werden auf der Grundlage der Anzahl ausgeführt arguments, die an dieselbe Funktion übergeben werden 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

HINWEIS - JS bietet keine eingebaute Fähigkeit zur Funktionsüberladung.

Alternative

John E Resig (Erfinder von JS) hat eine Alternative aufgezeigt, die die oben genannten Voraussetzungen nutzt, um die Fähigkeit zur Implementierung einer Funktionsüberladung zu erreichen.

Der folgende Code verwendet einen einfachen, aber naiven Ansatz unter Verwendung von if-elseoder switchAnweisung.

  • bewertet die argument-lengthEigenschaft.
  • Unterschiedliche Werte führen zum Aufruf unterschiedlicher Funktionen.

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

Eine andere Technik ist viel sauberer und dynamischer. Das Highlight dieser Technik ist die addMethodgenerische Funktion.

  • Wir definieren eine Funktion addMethod, mit der einem Objekt mit demselben Namen, aber unterschiedlichen Funktionen verschiedene Funktionen hinzugefügt werden .

  • Unterhalb der addMethodFunktion werden drei Parameter Objektname object, Funktionsname nameund die Funktion akzeptiert , die aufgerufen werden sollfn .

  • In der addMethodDefinition wird var oldder Verweis auf den vorherigen functiongespeichert, der mithilfe des Verschlusses gespeichert wird - eine Schutzblase.

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

  • Verwenden Sie den Debugger, um den Codefluss zu verstehen.
  • Im Folgenden werden addMethoddrei Funktionen hinzugefügt, die beim Aufrufen ninja.whatever(x)mit der Anzahl der Argumente, xdie beliebig sein können, dh entweder leer oder mit einem oder mehreren Argumenten , verschiedene Funktionen aufrufen, die unter Verwendung der addMethodFunktion definiert wurden.

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

Sagar Munjal
quelle
4

Eine andere Möglichkeit, dies zu erreichen, ist die Verwendung der speziellen Variablen: Argumente . Dies ist eine Implementierung:

function sum() {
    var x = 0;
    for (var i = 0; i < arguments.length; ++i) {
        x += arguments[i];
    }
    return x;
}

So können Sie diesen Code ändern in:

function sum(){
    var s = 0;
    if (typeof arguments[0] !== "undefined") s += arguments[0];
.
.
.
    return s;
}
Bashir Abdelwahed
quelle
3

Schau dir das an. Es ist sehr cool. http://ejohn.org/blog/javascript-method-overloading/ Trick Javascript, damit Sie Anrufe wie diesen tätigen können:

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
quelle
Hallo Jaider, sieh dir meine Antwort an, sie enthält Code für das tatsächliche Überladen von Javascript-Methoden. Ich spreche Func(new Point())und Func(new Rectangle())werde verschiedene Funktionen ausführen. Aber ich muss darauf hinweisen, dass dies ein schmutziger Hack ist, da das Überladen von Methoden wirklich eine Aufgabe zur Kompilierungszeit und keine Laufzeit ist.
Keldon Alleyne
3

Funktionsüberladung in Javascript:

Funktionsüberladung ist die Fähigkeit einer Programmiersprache, mehrere gleichnamige Funktionen mit unterschiedlichen Implementierungen zu erstellen. Wenn eine überladene Funktion aufgerufen wird, führt sie eine spezifische Implementierung dieser Funktion aus, die dem Kontext des Aufrufs entspricht. Dieser Kontext ist normalerweise die Anzahl der empfangenen Argumente und ermöglicht es einem Funktionsaufruf, sich je nach Kontext unterschiedlich zu verhalten.

Javascript verfügt nicht über eine integrierte Funktionsüberladung. Dieses Verhalten kann jedoch auf viele Arten emuliert werden. Hier ist eine bequeme einfache:

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

In Szenarien, in denen Sie nicht wissen, wie viele Argumente Sie erhalten, können Sie den Restoperator verwenden, der aus drei Punkten besteht .... Der Rest der Argumente wird in ein Array konvertiert. Achten Sie jedoch auf die Browserkompatibilität. Hier ist ein Beispiel:

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

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

Willem van der Veen
quelle
2

Da dieser Beitrag bereits viele verschiedene Lösungen enthält, dachte ich, ich poste eine andere.

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

}

Dies kann wie folgt verwendet werden:

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

Diese Lösung ist nicht perfekt, aber ich möchte nur zeigen, wie es gemacht werden kann.

remyH
quelle
2

Sie können die 'addMethod' von John Resig verwenden. Mit dieser Methode können Sie Methoden basierend auf der Anzahl der Argumente "überladen".

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

Ich habe auch eine Alternative zu dieser Methode erstellt, die Caching verwendet, um die Variationen der Funktion zu speichern. Die Unterschiede werden hier beschrieben

// 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);
  };
}
istavros
quelle
2

Weiterleitungsmuster => die beste Vorgehensweise bei JS-Überladung

Weiterleiten an eine andere Funktion, deren Name aus dem 3. und 4. Punkt besteht:

  1. Anzahl der Argumente verwenden
  2. Überprüfung der Argumenttypen
window['foo_'+arguments.length+'_'+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments)

Bewerbung für Ihren Fall:

 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(){

  }

Andere komplexe Probe:

      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 ....   
Abdennour TOUMI
quelle
2

Funktionsüberladung durch dynamischen Polymorphismus in 100 Zeilen JS

Dies wird von einem größeren Körper von Code, der beinhaltet isFn, isArrusw. Typen Prüffunktionen. Die folgende VanillaJS-Version wurde überarbeitet, um alle externen Abhängigkeiten zu entfernen. Sie müssen jedoch Ihre eigenen Typprüfungsfunktionen für die Verwendung in den .add()Aufrufen definieren.

Hinweis: Dies ist eine selbstausführende Funktion (so dass wir einen geschlossenen / geschlossenen Bereich haben können), daher die Zuordnung zu window.overloadund nicht 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;
}();

Verwendungszweck:

Der Aufrufer definiert seine überladenen Funktionen, indem er der Rückgabe von eine Variable zuweist overload(). Dank der Verkettung können die zusätzlichen Überlastungen in Reihe definiert werden:

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

Das einzige optionale Argument zum overload()Definieren der "Standard" -Funktion, die aufgerufen werden soll, wenn die Signatur nicht identifiziert werden kann. Die Argumente .add()sind:

  1. fn: functionDefinieren der Überlast;
  2. a_vArgumentTests: Arrayof functions definiert die Tests, die auf dem ausgeführt werden sollen arguments. Jeder functionakzeptiert ein einzelnes Argument und gibt trueIhr Argument zurück, basierend darauf, ob das Argument gültig ist.
  3. sAlias(Optional): stringWenn Sie den Alias ​​für den direkten Zugriff auf die Überladungsfunktion ( fn) definieren, myOverloadedFn.noArgs()wird diese Funktion z. B. direkt aufgerufen, wobei die dynamischen Polymorphismustests der Argumente vermieden werden.

Diese Implementierung ermöglicht tatsächlich mehr als nur herkömmliche Funktionsüberladungen, da das zweite a_vArgumentTestsArgument .add()in der Praxis benutzerdefinierte Typen definiert. Sie können also Argumente nicht nur anhand des Typs, sondern auch anhand von Bereichen, Werten oder Wertesammlungen steuern!

Wenn Sie die 145 Codezeilen overload()durchsehen, werden Sie feststellen, dass jede Signatur nach der Anzahl der argumentsübergebenen Signaturen kategorisiert ist . Dies geschieht, damit wir die Anzahl der durchgeführten Tests begrenzen. Ich verfolge auch die Anzahl der Anrufe. Mit etwas zusätzlichem Code könnten die Arrays überladener Funktionen neu sortiert werden, sodass häufig aufgerufene Funktionen zuerst getestet werden, wodurch wiederum ein gewisses Maß an Leistungssteigerung hinzugefügt wird.

Nun, es gibt einige Einschränkungen ... Da Javascript lose geschrieben ist, müssen Sie vorsichtig mit Ihrem sein, vArgumentTestsda integeres als floatusw. validiert werden könnte .

JSCompress.com-Version (1114 Byte, 744 Byte mit 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}();
Campbeln
quelle
2

Sie können jetzt in ECMAScript 2018 eine Funktionsüberladung durchführen, ohne Polyfills zu erstellen, die Länge / den Typ var zu überprüfen usw. Verwenden Sie einfach die 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"

Was ist Spread-Syntax?

Der Vorschlag Rest / Spread Properties for ECMAScript (Stufe 4) fügt Objektliteralen Spread-Eigenschaften hinzu. Es kopiert eigene aufzählbare Eigenschaften von einem bereitgestellten Objekt auf ein neues Objekt. Mehr zu mdn

Hinweis: Die Spread-Syntax in Objektliteralen funktioniert in Edge und IE nicht und ist eine experimentelle Funktion. Siehe Browserkompatibilität

AlienKevin
quelle
2

So etwas kann zur Funktionsüberlastung durchgeführt werden.

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

Quelle

Supun Kavinda
quelle
1

Dies ist eine alte Frage, aber eine, von der ich denke, dass sie einen anderen Eintrag benötigt (obwohl ich bezweifle, dass jemand sie lesen wird). Die Verwendung von sofort aufgerufenen Funktionsausdrücken (IIFE) kann in Verbindung mit Schließungen und Inline-Funktionen verwendet werden, um eine Funktionsüberladung zu ermöglichen. Betrachten Sie das folgende (erfundene) Beispiel:

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

Kurz gesagt, die Verwendung des IIFE schafft einen lokalen Bereich, der es uns ermöglicht, die private Variable oldzu definieren , um einen Verweis auf die ursprüngliche Definition der Funktion zu speichern foo. Diese Funktion gibt dann eine Inline-Funktion zurück newFoo, die den Inhalt beider Argumente protokolliert, wenn genau zwei Argumente übergeben werden, aund boder die oldFunktion if aufruft arguments.length !== 2. Dieses Muster kann beliebig oft wiederholt werden, um einer Variablen mehrere unterschiedliche Funktionsdefinitionen zu verleihen.

wvandaal
quelle
1

Ich möchte ein nützliches Beispiel für einen überlasteten Ansatz nennen.

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

Verwendung: Clear (); // Löscht das gesamte Dokument

Löschen (myDiv); // Löscht das von myDiv referenzierte Bedienfeld

Alexander Chernosvitov
quelle
1

JavaScript ist eine untypisierte Sprache, und ich denke nur, dass es sinnvoll ist, eine Methode / Funktion in Bezug auf die Anzahl der Parameter zu überladen. Daher würde ich empfehlen zu überprüfen, ob der Parameter definiert wurde:

myFunction = function(a, b, c) {
     if (b === undefined && c === undefined ){
          // do x...
     }
     else {
          // do y...
     }
};
Tony
quelle
1
Ich möchte nur beachten, dass untypisiert nicht "keine Typen" bedeutet.
Hamdiakoguz
1

Ab Juli 2017 ist das Folgende die übliche Technik. Beachten Sie, dass wir auch innerhalb der Funktion eine Typprüfung durchführen können.

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
Chong Lip Phang
quelle
1

Für Ihren Anwendungsfall würde ich so vorgehen ES6(da es bereits Ende 2017 ist):

const foo = (x, y, z) => {
  if (y && z) {
    // Do your foo(x, y, z); functionality
    return output;
  }
  // Do your foo(x); functionality
  return output;
}

Sie können dies natürlich anpassen, um mit einer beliebigen Anzahl von Parametern zu arbeiten, und einfach Ihre bedingten Anweisungen entsprechend ändern.

Barry Michael Doyle
quelle
1

In JS gibt es keine tatsächliche Überladung. Trotzdem können wir die Methodenüberladung auf verschiedene Arten simulieren:

Methode 1: Objekt verwenden

function test(x,options){
  if("a" in options)doSomething();
  else if("b" in options)doSomethingElse();
}
test("ok",{a:1});
test("ok",{b:"string"});

Methode 2: Verwenden Sie Rest (Spread) -Parameter

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

Methode 3: Verwenden Sie undefiniert

function test(x, y, z){
 if(typeof(z)=="undefined")doSomething();
}

Methode 4: Typprüfung

function test(x){
 if(typeof(x)=="string")console.log("a string passed")
 else ...
}
xx yy
quelle
1

Die Standardparameter werden zwar nicht überladen, können jedoch einige der Probleme lösen, mit denen Entwickler in diesem Bereich konfrontiert sind. Die Eingabe wird streng von der Reihenfolge bestimmt, Sie können nicht wie bei der klassischen Überladung nachbestellen:

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

Ergebnisse in (für das Jahr 2020):

2020
16160
12
65
2021
2023

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

Karl Morrison
quelle
0

Die erste Option verdient wirklich Aufmerksamkeit, da ich sie in einem recht komplexen Code-Setup gefunden habe. Meine Antwort lautet also

  1. Verwenden Sie zunächst verschiedene Namen

Mit einem kleinen, aber wesentlichen Hinweis sollten Namen für Computer anders aussehen, aber nicht für Sie. Nennen Sie überladene Funktionen wie: func, func1, func2.

Alehro
quelle
Ich wollte versuchen, zu überladen, entschied mich aber, nur andere Namen zu verwenden, z. B. getDeviceInfoByID und getDeviceInfoByType ...
CramerTV