it-swarm.com.de

Funktionsüberladung in Javascript - Best Practices

Was ist der beste Weg, um eine Überladung der Funktionen in Javascript zu fälschen? 

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

  1. In erster Linie verschiedene Namen verwenden
  2. Verwenden optionaler Argumente wie y = y || 'default'
  3. Anzahl der Argumente verwenden
  4. Argumenttypen prüfen
  5. Oder wie?
690
hamdiakoguz

Das Überladen von Funktionen mit Parametern ist am besten, wenn Sie die Argumentlänge oder -typen nicht überprüfen. Wenn Sie die Typen prüfen, wird Ihr Code nur langsam und Sie haben Spaß an Arrays, Nullen, Objekten usw.

Die meisten Entwickler verwenden ein Objekt als letztes Argument für ihre Methoden. 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 es in Ihrer Methode beliebig behandeln. [Wechseln, wenn-sonst usw.]

532
epascarello

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 in C #. Parameter, die nicht angegeben werden, sind in Javascript 'undefined', was in einer if-Anweisung zu false ausgewertet wird. Die Funktionsdefinition vermittelt jedoch nicht die Information, dass p2 und p3 optional sind. Wenn Sie viel Überladung benötigen, hat sich jQuery dafür entschieden, ein Objekt als Parameter zu verwenden, beispielsweise jQuery.ajax (Optionen). Ich stimme ihnen zu, dass dies der effektivste und eindeutig dokumentierbare Ansatz für das Überladen ist, aber ich brauche selten mehr als ein oder zwei optionale Parameter.

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

150
rocketsarefast

Es gibt keine echte Funktionsüberladung in JavaScript, da hier beliebig viele Parameter übergeben werden können. Sie müssen in der Funktion überprüfen, wie viele Argumente übergeben wurden und um welchen Typ es sich handelt.

64
Gumbo

Die richtige Antwort lautet: IS KEIN ÜBERLADEN IN JAVASCRIPT.

Das Überprüfen/Umschalten innerhalb der Funktion ist nicht ÜBERLAST.

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 spezifische Implementierung dieser Funktion aus, die für den Kontext des Aufrufs geeignet ist, sodass ein Funktionsaufruf je nach Kontext verschiedene 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 erfordert und mit einem leeren Parameterfeld aufgerufen wird. Ein häufiger Fehler wäre, dem Objekt in der zweiten Methode einen Standardwert zuzuweisen, der zu einem mehrdeutigen Aufruffehler führen würde, da der Compiler nicht wissen würde, 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.

46
Marco

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 wünschen 

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

26
t3rse

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

Edit (2018): Seit 2011 geschrieben wurde, hat die Geschwindigkeit direkter Methodenaufrufe stark zugenommen, die Geschwindigkeit überladener Methoden jedoch nicht.

Es ist kein Ansatz, den ich empfehlen würde, aber es ist eine sinnvolle Denkübung, um 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 das Überladen von Funktionen (unter Berücksichtigung von Typen) in 13.0 langsamer in Google Chrome V8 ab 16.0 (beta) sein kann.

Neben dem Übergeben eines Objekts (d. H. {x: 0, y: 0}) kann man gegebenenfalls auch den C-Ansatz wählen und die Methoden entsprechend benennen. Zum Beispiel Vector.AddVector (vector), Vector.AddIntegers (x, y, z, ...) und Vector.AddArray (integerArray). Sie können sich C-Bibliotheken wie OpenGL zur Benennung von Inspiration ansehen. 

Edit: Ich habe einen Benchmark für das Übergeben eines Objekts und das Testen des Objekts mit 'param' in arg und arg.hasOwnProperty('param') hinzugefügt. Das Überladen von Funktionen ist viel schneller als das Übergeben eines Objekts und das Überprüfen von Eigenschaften (in diesem Benchmark mindestens).

Aus konstruktiver Sicht ist die Funktionsüberladung nur gültig oder logisch, wenn die überladenen Parameter derselben Aktion entsprechen. Es liegt daher nahe, dass es eine zugrunde liegende Methode geben sollte, die sich nur mit bestimmten Details befasst, ansonsten könnte dies auf unangemessene Designentscheidungen hindeuten. So könnte man auch die Verwendung von Funktionsüberladung auflösen, indem Daten in ein entsprechendes Objekt konvertiert werden. Natürlich muss man den Umfang des Problems in Betracht ziehen, da keine aufwendigen Entwürfe erforderlich sind, wenn nur ein Name gedruckt werden soll. Für das Design von Frameworks und Bibliotheken ist dies jedoch gerechtfertigt.

Mein Beispiel stammt aus einer Rechteckimplementierung - daher die Erwähnung von Dimension und Punkt. Vielleicht könnte Rectangle dem Prototyp Dimension und Point eine GetRectangle()-Methode hinzufügen, und dann wird das Problem mit der Funktionsüberladung sortiert. Und was ist mit Primitiven? Nun, wir haben Argumentlänge, was 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);
17
Keldon Alleyne

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 normalerweise in der folgenden Reihenfolge, bis einer von ihnen funktioniert:

  1. Verwendung optionaler Argumente wie y = y || 'Standard'. Dies ist praktisch, wenn Sie es können, aber es funktioniert nicht immer praktisch, z. wenn 0/null/undefined ein gültiges Argument wäre.

  2. Anzahl der Argumente verwenden. Ähnlich wie die letzte Option, kann jedoch funktionieren, 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. In erster Linie verschiedene Namen verwenden. Möglicherweise müssen Sie dies tun, wenn die anderen Optionen nicht funktionieren, nicht praktikabel sind oder mit anderen verwandten Funktionen übereinstimmen.

16
Matthew Crumley

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

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

Ich denke, eine der Möglichkeiten, die für den Großteil der Fälle geeignet ist, ist folgende: 

Nehmen wir an, Sie haben eine Methode 

function foo(x)
{
} 

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

fooNew(x,y,z)
{
}

und modifizieren Sie dann die 1. Funktion wie folgt:

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

Bei vielen solchen überladenen Methoden sollten Sie switch anstelle von if-else-Anweisungen in Betracht ziehen.

( mehr Details )

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

13
Aniket Thakur

Ich bin nicht sicher, was die beste Praxis ist, aber so mache ich es:

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

Ich habe es gerade ausprobiert, vielleicht entspricht es Ihren Bedürfnissen ... Je nach Anzahl der Argumente können Sie auf eine andere Funktion zugreifen. Sie initialisieren es, wenn Sie es zum ersten Mal aufrufen. Die Funktionskarte ist in der Schließung 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");
6
AntouanK

Da JavaScript keine Funktion für Überladungsoptionen hat, können stattdessen Objekte verwendet werden. Wenn es ein oder zwei erforderliche Argumente gibt, ist es besser, sie vom Optionsobjekt zu trennen. Hier ein Beispiel, wie Sie das Optionsobjekt und aufgefüllte Werte als Standardwert verwenden, wenn der Wert nicht im Optionsobjekt ü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 + "}";

}

here ist ein Beispiel für die Verwendung von Optionsobjekt

5
Vlad Bezden

Es gibt keine Möglichkeit zum Überladen von Funktionen in Javascript . Daher empfehle ich Folgendes durch die typeof()-Methode anstelle von

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!

4
Smith Lee

EINFÜHRUNG

Das Durchlesen so vieler Antworten würde jedem Kopfschmerzen bereiten. Jeder, der versucht, das Konzept zu kennen, muss die folgende Voraussetzung s kennen.

Function overloading Definition, Function Length property, Function argument property

Function overloading In seiner einfachsten Form bedeutet, dass eine Funktion verschiedene Aufgaben auf der Basis der Anzahl der Argumente ausführt, die an sie übergeben werden. Insbesondere TASK1, TASK2 und TASK3 sind unten hervorgehoben und werden auf der Basis der Anzahl von arguments ausgeführt, die an dieselbe Funktion fooYo übergeben werden.

// 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 bietet keine eingebaute Fähigkeit zur Funktionsüberladung.

Alternativ

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

Der folgende Code verwendet einen einfachen, aber naiven Ansatz mit der Anweisung if-else Oder switch.

  • wertet die Eigenschaft argument-length aus.
  • unterschiedliche Werte rufen unterschiedliche Funktionen auf.
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 generische Funktion addMethod.

  • wir definieren eine Funktion addMethod, mit der einem Objekt mit dem gleichen Namen aber verschiedenen Funktionen verschiedene Funktionen hinzugefügt werden.

  • unterhalb der Funktion addMethod werden drei Parameter Objektname object, Funktionsname name und die Funktion akzeptiert, die aufgerufen werden soll fn.

  • In der addMethod -Definition var old Wird der Verweis auf das vorherige function gespeichert, das 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.
  • unterhalb von addMethod werden drei Funktionen hinzugefügt, die beim Aufrufen mit ninja.whatever(x) mit der Anzahl der Argumente x, die beliebig sein können, dh entweder leer sein oder eine oder mehrere verschiedene Funktionen aufrufen als Wird definiert, während die Funktion addMethod verwendet wird.
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
Sagar Munjal

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

sie können diesen Code also folgendermaßen ändern:

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

schau dir das an. Es ist sehr cool . http://ejohn.org/blog/javascript-method-overloading/ Trick Javascript, um Anrufe wie folgt zu ermöglichen:

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
3
Jaider

Da dieser Beitrag bereits viele verschiedene Lösungen enthält, habe ich gedacht, 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 demonstrieren, wie dies möglich ist.

2
remyH

Sie können jetzt in ECMAScript 2018 eine Funktionsüberladung durchführen, ohne Polyfills, Var-Länge/Typ usw. zu überprüfen. 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-Eigenschaften für ECMAScript (Stufe 4) fügt Objektliteralen Spread-Eigenschaften hinzu. Es kopiert eigene aufzählbare Eigenschaften von einem bereitgestellten Objekt auf ein neues Objekt. Mehr auf Mdn

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

2
AlienKevin

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, bei der die Variationen der Funktion im Caching gespeichert werden. 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);
  };
}
2
istavros

Funktionsüberladung durch dynamischen Polymorphismus in 100 Zeilen von JS

Dies ist aus einem größeren Code, der die Typüberprüfungsfunktionen isFn, isArr usw. enthält. Die folgende VanillaJS-Version wurde überarbeitet, um alle externen Abhängigkeiten zu entfernen. Sie müssen jedoch eigene Typüberprüfungsfunktionen für die Verwendung in den .add()-Aufrufen definieren.

Hinweis: Dies ist eine selbstausführende Funktion (daher können wir einen abgeschlossenen/geschlossenen Bereich haben), daher die Zuweisung zu window.overload und nicht zu 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 overload() eine Variable zuweist. 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 für overload() definiert die "default" -Funktion, die aufgerufen werden soll, wenn die Signatur nicht identifiziert werden kann. Die Argumente für .add() sind:

  1. fn: function definiert die Überlast;
  2. a_vArgumentTests: Array von functions definiert die Tests, die auf der arguments ausgeführt werden sollen. Jede function akzeptiert ein einzelnes Argument und gibt truethy basierend auf dem gültigen Argument zurück.
  3. sAlias (optional): string Definiert den Alias ​​für den direkten Zugriff auf die Überladungsfunktion (fn), z. myOverloadedFn.noArgs() ruft diese Funktion direkt auf, wodurch die dynamischen Polymorphismustests der Argumente vermieden werden.

Diese Implementierung ermöglicht tatsächlich mehr als nur traditionelle Funktionsüberladungen, da das zweite a_vArgumentTests-Argument von .add() in der Praxis benutzerdefinierte Typen definiert. Sie können also Argumente nicht nur nach Typ, sondern nach Bereichen, Werten oder Wertesammlungen durchlaufen!

Wenn Sie die 145 Codezeilen nach overload() durchsehen, werden Sie feststellen, dass jede Signatur nach der Nummer der arguments kategorisiert wird, die an sie übergeben wird. Dies geschieht, um die Anzahl der durchgeführten Tests zu begrenzen. Ich verfolge auch die Anrufzählung. Mit etwas zusätzlichem Code könnten die Arrays überladener Funktionen neu sortiert werden, so dass zuerst aufgerufene Funktionen zuerst getestet werden, was wiederum ein gewisses Maß an Leistungsverbesserung hinzufügt.

Nun, es gibt einige Vorbehalte ... Da Javascript nur lose eingegeben wird, müssen Sie mit Ihrer vArgumentTests vorsichtig sein, da eine integer als float validiert werden könnte usw.

JSCompress.com-Version (1114 Byte, 744 Byte g-gezippt):

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
Campbeln

Forwarding Pattern => Best Practice für das Überladen von JS

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

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

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

Die erste Option verdient wirklich Aufmerksamkeit, denn ich habe es in einem ziemlich komplexen Code-Setup gefunden. Meine Antwort lautet also

  1. In erster Linie verschiedene Namen verwenden

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

1
alehro

Für Ihren Anwendungsfall würde ich es mit ES6 angehen (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 Ihre Bedingungsanweisungen entsprechend zu ändern.

1

JavaScript ist eine nicht typisierte 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 prüfen, ob der Parameter definiert wurde:

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

Seit Juli 2017 ist die übliche Technik das Folgende. Beachten Sie, dass wir auch eine Typprüfung innerhalb der Funktion 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
1
Chong Lip Phang

Dies ist eine alte Frage, aber eine, von der ich glaube, dass sie einen weiteren Eintrag benötigt (obwohl ich bezweifle, dass jemand sie lesen wird). Die Verwendung der sofort einberufenen Funktionsausdrücke (IIFE) kann in Verbindung mit Schließungen und Inline-Funktionen verwendet werden, um Funktionsüberlastung 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, durch die Verwendung des IIFE wird ein lokaler Bereich erstellt, der es uns ermöglicht, die private Variable old zu definieren, um einen Verweis auf die ursprüngliche Definition der Funktion foo zu speichern. Diese Funktion gibt dann eine Inline-Funktion newFoo zurück, die den Inhalt beider Argumente protokolliert, wenn genau zwei Argumente a und b übergeben werden, oder die Funktion old aufgerufen wird, wenn arguments.length !== 2. Dieses Muster kann beliebig oft wiederholt werden, um eine Variable mit verschiedenen Funktionsunterschieden auszustatten.

1
wvandaal

So etwas kann zur Funktionsüberladung gemacht 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

0
Supun Kavinda

Ich möchte ein nützliches Beispiel für einen überladenen Ansatz vorstellen.

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

Verwendungszweck: Klar(); // Löscht das gesamte Dokument

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

Ich benutze diese Funktion seit Jahren, um meine Überlastungen zu verbessern:

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

Demostrated:

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

Sehen Sie sich die off-Methode von jQuery an:

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

Viele überlastete Funktionen sind bei Performanceoptimierung nahezu unlesbar. Sie müssen den Kopf der Funktion entschlüsseln. Dies ist möglicherweise schneller als die Verwendung einer overload-Funktion, wie ich sie vorstelle. Es ist jedoch aus menschlicher Sicht langsamer, um festzustellen, welche Überladung aufgerufen wurde.

0
Mario

es gibt keine tatsächliche Überladung in JS, 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: Rest (Spread) Parameter verwenden

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: verwende undefined

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 ...
}
0
xx yy

Wir haben over.js gemacht, um dieses Problem zu lösen, ist ein sehr eleganter Weg. Du kannst tun:

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

};
0
Mat Ryer

@ AntouanKs Ansatz gefällt mir. Ich finde oft, dass ich eine Funktion mit verschiedenen Nummern oder Parametern und verschiedenen Typen anbiete. Manchmal folgen sie keiner Bestellung. Ich verwende, um die Typen von Parametern abzubilden:

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);
}
0
Andre Medeiros

Also gefiel mir diese Art, Dinge zu tun, die ich in den Geheimnissen des Javascript-Ninja gefunden habe

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

sie verwenden dann addMethod, um überlastete Funktionen zu jedem Objekt hinzuzufügen. Die Hauptverwirrung in diesem Code war für mich die Verwendung von fn.length == arguments.length - dies funktioniert, weil fn.length die Anzahl der erwarteten Parameter ist, während arguments.length die Anzahl der Parameter ist, die tatsächlich mit aufgerufen werden Funktion. Der Grund, warum die anonyme Funktion kein Argument hat, liegt darin, dass Sie eine beliebige Anzahl von Argumenten in Javascript übergeben können und die Sprache verzeihend ist. 

Ich mochte das, weil Sie es überall verwenden können - erstellen Sie einfach diese Funktion und verwenden Sie einfach die Methode in der gewünschten Codebasis. 

Es vermeidet auch eine lächerlich große if/switch-Anweisung, die schwer zu lesen ist, wenn Sie mit dem Schreiben von komplexem Code beginnen (die akzeptierte Antwort führt dazu).

In Bezug auf die Nachteile, denke ich, ist der Code anfangs ein wenig obskur ... aber ich bin mir bei anderen nicht sicher? 

0
praks5432

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. In diesem Kontext wird in der Regel die Anzahl der Argumente angegeben, die empfangen werden. Je nach Kontext kann sich ein Funktionsaufruf unterschiedlich verhalten.

Javascript nicht hat eine eingebaute 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 Operator Rest verwenden, der drei Punkte ... enthält. Der Rest der Argumente wird in ein Array konvertiert. Achten Sie jedoch auf Browserkompatibilität. Hier ist ein Beispiel:

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

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