Przeciążenie funkcji w JavaScript - najlepsze praktyki


783

Jaki jest najlepszy sposób na fałszywe przeciążenie funkcji w JavaScript?

Wiem, że nie jest możliwe przeciążenie funkcji w Javascript, tak jak w innych językach. Gdybym potrzebował funkcji z dwoma zastosowań foo(x)i foo(x,y,z)który jest najlepszy / preferowany sposób:

  1. Używanie przede wszystkim różnych nazw
  2. Używanie opcjonalnych argumentów, takich jak y = y || 'default'
  3. Korzystanie z liczby argumentów
  4. Sprawdzanie typów argumentów
  5. Albo jak?

14
Być może warto zapytać, dlaczego uważasz, że potrzebujesz przeciążenia funkcji na początek. Myślę, że to przybliży nas do prawdziwego rozwiązania.
Breton

1
To jest zamknięte, ale wykonuję następujące czynności: this.selectBy = {instance: selectByInstance, // Tekst funkcji: selectByText, // Wartość funkcji: selectByValue // Funkcja};
Więzień ZERO

Moja odpowiedź pokazuje, jak wykonać przeładowanie funkcji w czasie wykonywania, ma ona ograniczenie prędkości i nie radziłbym, aby obejść specyfikację Javascript. Przeciążenie funkcji jest tak naprawdę zadaniem czasowym kompilacji, udzielam odpowiedzi tylko w celach akademickich i pozostawiam ją według własnego uznania, czy zastosować ją w kodzie, czy nie.
Keldon Alleyne

2
Na wszelki wypadek zbudowałem lekki framework js, który pozwala na przeciążanie metod opartych na typach. Oczywiście te same zastrzeżenia dotyczą wydajności, ale do tej pory działało dobrze na moje potrzeby i wciąż ma sporo miejsca na ulepszenia: blog.pebbl.co.uk/2013/01/describejs.html#methodoverloading
Pebbl

Odpowiedzi:


602

Najlepszym sposobem na przeciążenie funkcji parametrami nie jest sprawdzenie długości argumentów ani typów; sprawdzanie typów po prostu spowolni kod i będziesz się dobrze bawić tablicami, zerami, obiektami itp.

Większość programistów robi sprzężenie z obiektem jako ostatnim argumentem swoich metod. Ten obiekt może pomieścić wszystko.

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

Następnie możesz obsłużyć go w dowolny sposób w metodzie. [Switch, if-else itp.]


43
Czy możesz podać przykładową implementację foo (), która ilustruje, w jaki sposób te parametry „opts” są używane / do których się odwołuje?
Moe Howard

24
Moe // Może tak być; if(opts['test']) //if test param exists, do something.. if(opts['bar']) //if bar param exists, do something
Deckard,

80
To nie jest przeciążanie funkcji. Przeciążenie funkcji ma dwie osobne funkcje o tej samej nazwie, ale o różnych parametrach. Opisujesz tylko jedną funkcję z argumentem obiektowym na końcu.
d512

66
@ user1334007 nie jest możliwe przeciążenie funkcji, tak jak w Java / .NET. Tak, to nie jest „dokładnie” przeciążenie, ale działa.
epascarello

18
Dziwi mnie, że nikt już o to nie pytał: dlaczego sprawdzanie arguments.lengthnie jest zalecane? Byłem tu już wcześniej i czytałem To, co robi większość programistów, to ... ale jestem pewien, że to jedyne miejsce, w którym to widziałem. Ta metoda psuje także składniową cukierkowość „przeciążeń”!
c24w

169

Często to robię:

DO#:

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

Odpowiednik 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

Ten konkretny przykład jest w rzeczywistości bardziej elegancki w javascript niż C #. Parametry, które nie są określone, są „niezdefiniowane” w javascript, co w instrukcji if powoduje wyrażenie false. Jednak definicja funkcji nie przekazuje informacji, że p2 i p3 są opcjonalne. Jeśli potrzebujesz dużo przeciążenia, jQuery zdecydowało się użyć obiektu jako parametru, na przykład jQuery.ajax (opcje). Zgadzam się z nimi, że jest to najskuteczniejsze i najlepiej udokumentowane podejście do przeciążania, ale rzadko potrzebuję więcej niż jednego lub dwóch szybkich parametrów opcjonalnych.

EDYCJA: zmieniono test IF zgodnie z sugestią Iana


16
Nieokreślone parametry znajdują się undefinedw JS, a nie null. Najlepszą praktyką jest, aby nigdy niczego nie ustawiać undefined, więc nie powinno to stanowić problemu, o ile zmienisz test na p2 === undefined.
Tamzin Blake,

3
Jeśli podasz falsejako ostatni argument, nie będzie on konkatenowany "false"do końca, ponieważ if(p3)nie rozgałęzi się.
dreamlax

5
Krótka uwaga, typeof p2 === "undefined"prawdopodobnie jest to odwrotność tego, czego oczekujesz w przypadku twojego przykładu, myślę, że typeof p2 !== "undefined"to, co zamierzałeś. Ponadto, czy mogę zasugerować, że ma on łączyć łańcuch, liczbę i wartość logiczną, co faktycznie robiszp2 === "number"; p3 === "boolean"
WillFM

8
Lubię to robić: p3 = p3 || 'domyślna wartość';
Dorian

3
Jakie jest znaczenie ===i !==? Dlaczego nie po prostu użyć ==i !=?
Ricardo Cruz,

76

W JavaScript nie ma przeciążenia rzeczywistej funkcji, ponieważ pozwala ona na przekazanie dowolnej liczby parametrów dowolnego typu. Musisz sprawdzić wewnątrz funkcji, ile argumentów zostało przekazanych i jakiego typu są.


1
John Resig (z jQuery) kiedyś próbował tego, ale próba była czysto akademicka i nie przyniosła żadnych rzeczywistych korzyści.
scunliffe

14
Przeciążenie funkcji Johna Resiga tutaj ejohn.org/blog/javascript-method-overloading
Terrance

@Terrance: Lubię też metodę Resiga. To działa jak urok. Muszę tylko znaleźć sposób na utworzenie testu, aby zweryfikować przypadki użycia.
chrisvillanueva

„Ta funkcja nie zmieni świata, ale jest krótka, zwięzła i używa niejasnej funkcji JavaScript - więc wygrywa w mojej książce”. :-)
Frerich Raabe

67

Prawidłowa odpowiedź brzmi: NIE JEST PRZEKŁADANIE W JAVASCRIPT.

Sprawdzanie / przełączanie wewnątrz funkcji nie jest PRZEGLĄDANIE.

Pojęcie przeciążenia: W niektórych językach programowania przeciążenie funkcji lub przeciążenie metody to możliwość tworzenia wielu metod o tej samej nazwie z różnymi implementacjami. Wywołania funkcji przeciążonej będą uruchamiać określoną implementację tej funkcji odpowiednią do kontekstu wywołania, umożliwiając jednemu wywołaniu funkcji wykonywanie różnych zadań w zależności od kontekstu.

Na przykład doTask () i doTask (obiekt O) są metodami przeciążonymi. Aby wywołać to drugie, obiekt musi zostać przekazany jako parametr, podczas gdy ten pierwszy nie wymaga parametru i jest wywoływany z pustym polem parametru. Częstym błędem byłoby przypisanie wartości domyślnej do obiektu w drugiej metodzie, co spowodowałoby dwuznaczny błąd wywołania, ponieważ kompilator nie wiedziałby, której z dwóch metod należy użyć.

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

Wszystkie sugerowane implementacje są świetne, ale prawdę mówiąc, nie ma natywnej implementacji dla JavaScript.


4
wreszcie normalna odpowiedź! W JAVASCRIPT NIE MA PRZEŁADUNKU.
Razvan Tudorica,

4
OP poprosił o sposób na fałszywe przeciążenie.
Ateur Games,

Jak powiedziałem wcześniej, jesteśmy tutaj, aby edukować ludzi, nie tylko udzielamy im odpowiedzi bez sprawdzenia, czy to, o co proszą, jest słuszne.
Marco,

27

Istnieją dwa sposoby, aby podejść do tego lepiej:

  1. Przekaż słownik (tablicę asocjacyjną), jeśli chcesz pozostawić dużą elastyczność

  2. Weź obiekt jako argument i użyj dziedziczenia opartego na prototypach, aby zwiększyć elastyczność.


1
taka była moja pierwotna myśl, jednak jeśli tworzona funkcja ma być używana w bibliotece lub przez inne osoby, wyliczenie wartości po prostu może być pomocne
roberthuttinger

19

Oto podejście, które pozwala na rzeczywiste przeciążenie metody przy użyciu typów parametrów, pokazane poniżej:

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

Edycja (2018) : Odkąd napisano to w 2011 r., Szybkość bezpośrednich wywołań metod znacznie wzrosła, podczas gdy prędkość przeciążonych metod nie.

Nie polecam takiego podejścia, ale warto zastanowić się nad tym, jak rozwiązać tego rodzaju problemy.


Oto punkt odniesienia dla różnych podejść - https://jsperf.com/function-overloading . To pokazuje, że funkcja przeciążenia (biorąc pod uwagę typy) może być około 13 razy wolniej w Google V8 Chrome jak z 16,0 (beta) .

Oprócz przekazywania obiektu (tj. {x: 0, y: 0}), Można również zastosować podejście C, gdy jest to właściwe, odpowiednio nazywając metody. Na przykład Vector.AddVector (wektor), Vector.AddIntegers (x, y, z, ...) i Vector.AddArray (integerArray). Możesz spojrzeć na biblioteki C, takie jak OpenGL, na inspirację nazewnictwa.

Edit : Dodałem odniesienia do przekazywania obiektu i testowania dla obiektu przy użyciu zarówno 'param' in arga arg.hasOwnProperty('param'), a funkcja przeciążenia jest znacznie szybsze niż przekazując obiekt oraz sprawdzenie właściwości (w tym teście co najmniej).

Z perspektywy projektowej przeciążenie funkcji jest poprawne lub logiczne tylko wtedy, gdy przeciążone parametry odpowiadają tej samej akcji. Jest więc oczywiste, że powinna istnieć podstawowa metoda, która dotyczy tylko określonych szczegółów, w przeciwnym razie może to wskazywać na niewłaściwe wybory projektowe. Można więc również rozwiązać problem nadmiernego obciążenia funkcji, konwertując dane do odpowiedniego obiektu. Oczywiście należy wziąć pod uwagę zakres problemu, ponieważ nie ma potrzeby robienia skomplikowanych projektów, jeśli zamierzasz po prostu wydrukować nazwę, ale w przypadku projektowania ram i bibliotek taka myśl jest uzasadniona.

Mój przykład pochodzi z implementacji Rectangle - stąd wzmianka o Dimension i Point. Być może Rectangle mógłby dodać GetRectangle()metodę do Dimensioni Pointprototypu, a następnie problem z przeciążeniem funkcji zostanie rozwiązany. A co z prymitywami? Mamy długość argumentów, która jest teraz poprawnym testem, ponieważ obiekty mają GetRectangle()metodę.

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

Najlepszy sposób naprawdę zależy od funkcji i argumentów. Każda z opcji jest dobrym pomysłem w różnych sytuacjach. Zasadniczo próbuję je w następującej kolejności, dopóki jeden z nich nie zadziała:

  1. Używanie opcjonalnych argumentów, takich jak y = y || 'domyślna'. Jest to wygodne, jeśli możesz to zrobić, ale nie zawsze może działać praktycznie, np. Gdy 0 / null / undefined byłoby poprawnym argumentem.

  2. Korzystanie z liczby argumentów. Podobne do ostatniej opcji, ale może działać, gdy nr 1 nie działa.

  3. Sprawdzanie typów argumentów. Może to działać w niektórych przypadkach, gdy liczba argumentów jest taka sama. Jeśli nie możesz w wiarygodny sposób określić typów, może być konieczne użycie różnych nazw.

  4. Używanie przede wszystkim różnych nazw. Być może trzeba to zrobić, jeśli inne opcje nie będą działać, nie są praktyczne lub w celu zachowania spójności z innymi powiązanymi funkcjami.


14

Jeśli potrzebuję funkcji z dwoma zastosowaniami foo (x) i foo (x, y, z), który jest najlepszy / preferowany sposób?

Problem polega na tym, że JavaScript NIE obsługuje przeciążania metod. Tak więc, jeśli zobaczy / przeanalizuje dwie lub więcej funkcji o tych samych nazwach, po prostu weźmie pod uwagę ostatnią zdefiniowaną funkcję i zastąpi poprzednie.

Jeden ze sposobów, który moim zdaniem jest odpowiedni dla większości przypadków, jest następujący:

Powiedzmy, że masz metodę

function foo(x)
{
} 

Zamiast metody przeciążania, która nie jest możliwa w javascript , możesz zdefiniować nową metodę

fooNew(x,y,z)
{
}

a następnie zmodyfikuj pierwszą funkcję w następujący sposób -

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

Jeśli masz wiele takich przeciążonych metod, rozważ użycie więcej switchniż tylko if-elseinstrukcji.

( więcej szczegółów )

PS: Powyższy link prowadzi do mojego osobistego bloga, który zawiera dodatkowe informacje.


9

Nie jestem pewien co do najlepszych praktyk, ale oto jak to zrobić:

/*
 * 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: Dodałem kilka, mam nadzieję, pomocnych komentarzy.
chessweb,

6

Właśnie tego spróbowałem, może odpowiada Twoim potrzebom. W zależności od liczby argumentów można uzyskać dostęp do innej funkcji. Inicjujesz go przy pierwszym wywołaniu. A mapa funkcji jest ukryta w zamknięciu.

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

Sprawdź to.

TEST.multiFn("0")
    .multiFn()
    .multiFn("0","1","2");

5

Ponieważ JavaScript nie ma funkcji przeciążenia funkcji, zamiast tego można użyć obiektu. Jeśli wymagany jest jeden lub dwa argumenty, lepiej trzymać je oddzielnie od obiektu opcji. Oto przykład, jak użyć obiektu opcji i zapełnionych wartości do wartości domyślnej w przypadku, gdy wartość nie została przekazana w obiekcie opcji.

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

}

oto przykład użycia obiektu opcji


4

W javascript nie ma sposobu na przeciążenie funkcji. Dlatego polecam następujące typeof()metody zamiast wielu funkcji w celu fałszywego przeciążenia.

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

Powodzenia!


24
Na litość Boską! Użyj instrukcji switch!
jedmao

8
Ponadto, jeśli nalegasz, aby nie używać przełącznika, powinieneś zadzwonić do typeof tylko raz. var type = typeof param; if (type === 'string') ...
Walter Stabosz

+1, aby skomentować „===”. Inną zaletą instrukcji switch jest to, czy (... == ...) jest bezpieczna dla typu.
Nathan Cooper

4

WPROWADZENIE

Jak dotąd przeczytanie tak wielu odpowiedzi sprawiłoby każdemu ból głowy. Każdy, kto próbuje poznać tę koncepcję, musi znać następujące warunki wstępne .

Function overloading Definition, Function Length property,Function argument property

Function overloadingw najprostszej formie oznacza, że ​​funkcja wykonuje różne zadania na podstawie liczby przekazywanych jej argumentów. W szczególności TASK1, TASK2 i TASK3 są podświetlone poniżej i są wykonywane na podstawie liczby argumentsprzekazanych do tej samej funkcji 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

UWAGA - JS nie zapewnia wbudowanej możliwości przeciążenia funkcji.

Alternatywny

John E Resig (twórca JS) wskazał alternatywę, która wykorzystuje powyższe warunki wstępne, aby osiągnąć zdolność do implementacji przeciążenia funkcji.

Poniższy kod wykorzystuje proste, ale naiwne podejście za pomocą if-elselub switchinstrukcji.

  • ocenia argument-lengthwłaściwość.
  • różne wartości powodują wywoływanie różnych funkcji.

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

Inna technika jest znacznie bardziej czysta i dynamiczna. Najważniejszym punktem tej techniki jest addMethodfunkcja ogólna.

  • definiujemy funkcję, addMethodktóra służy do dodawania różnych funkcji do obiektu o tej samej nazwie, ale o różnych funkcjach .

  • poniżej addMethodfunkcja akceptuje nazwę obiektu trzech parametrów, nazwę objectfunkcji namei funkcję, którą chcemy wywołać fn.

  • addMethodDefinicja wewnętrzna var oldprzechowuje odniesienie do poprzedniego, które functionjest przechowywane za pomocą zamknięcia - bańki ochronnej.

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

  • użyj debuggera, aby zrozumieć przepływ kodu.
  • poniżej addMethoddodaje trzy funkcje, które przy wywołaniu przy użyciu ninja.whatever(x)liczby argumentów, xktóre mogą być dowolne, tj. albo puste, albo jedna lub więcej niż jedna, wywołuje różne funkcje określone podczas korzystania z addMethodfunkcji.

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

Innym sposobem podejścia do tego jest użycie specjalnej zmiennej: arguments , jest to implementacja:

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

dzięki czemu możesz zmodyfikować ten kod, aby:

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

3

Spójrz na to. To jest bardzo fajne. http://ejohn.org/blog/javascript-method-overloading/ Trick Javascript, aby umożliwić wykonywanie takich połączeń:

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

Cześć Jaider, sprawdź moją odpowiedź, zawiera kod do faktycznego przeciążenia metody javascript. Mówię Func(new Point())i Func(new Rectangle())będę wykonywać różne funkcje. Ale muszę zaznaczyć, że jest to brudny hack, ponieważ przeciążenie metody jest tak naprawdę zadaniem czasu kompilacji, a nie czasu wykonywania.
Keldon Alleyne

3

Przeciążenie funkcji w JavaScript:

Przeciążenie funkcji to zdolność języka programowania do tworzenia wielu funkcji o tej samej nazwie z różnymi implementacjami. wywołanie funkcji przeciążonej spowoduje uruchomienie określonej implementacji tej funkcji, stosownie do kontekstu wywołania. Ten kontekst jest zwykle liczbą otrzymywanych argumentów i pozwala jednemu wywołaniu funkcji zachowywać się inaczej w zależności od kontekstu.

JavaScript nie ma wbudowanego przeciążenia funkcji. To zachowanie można jednak emulować na wiele sposobów. Oto wygodny prosty:

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

W scenariuszach, w których nie wiesz, ile argumentów otrzymasz, możesz użyć operatora reszty, który jest trzema kropkami .... Przekształci resztę argumentów w tablicę. Uważaj jednak na kompatybilność przeglądarki. Oto przykład:

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

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


2

Ponieważ ten post zawiera już wiele różnych rozwiązań, pomyślałem, że opublikuję kolejne.

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

}

można tego użyć, jak pokazano poniżej:

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

To rozwiązanie nie jest idealne, ale chcę tylko pokazać, jak można to zrobić.


2

Możesz użyć „addMethod” z John Resig. Za pomocą tej metody można „przeciążać” metody na podstawie liczby argumentów.

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

Stworzyłem również alternatywę dla tej metody, która wykorzystuje buforowanie do przechowywania odmian funkcji. Różnice opisano tutaj

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

Forwarding Pattern => najlepsza praktyka w zakresie przeciążania JS

Przejdź do innej funkcji, której nazwa jest zbudowana z 3 i 4 punktu:

  1. Korzystanie z liczby argumentów
  2. Sprawdzanie typów argumentów
window['foo_'+arguments.length+'_'+Array.from(arguments).map((arg)=>typeof arg).join('_')](...arguments)

Zastosowanie w twojej sprawie:

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

  }

Inna złożona próbka:

      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

Przeciążenie funkcji przez dynamiczny polimorfizm w 100 liniach JS

Jest to z większego ciała kodu, który obejmuje isFn, isArritd funkcji Kontrola typów. Poniższa wersja VanillaJS została przerobiona w celu usunięcia wszystkich zewnętrznych zależności, jednak będziesz musiał zdefiniować własne funkcje sprawdzania typu do użycia w .add()wywołaniach.

Uwaga: Jest to funkcja samowykonująca się (dzięki czemu możemy mieć zakres zamknięcia / zamknięcia), stąd przypisanie do window.overloadzamiast 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;
}();

Stosowanie:

Program wywołujący określa ich przeciążone funkcje, przypisując zmienną do zwracanej wartości overload(). Dzięki łączeniu dodatkowe przeciążenia można zdefiniować szeregowo:

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

Pojedynczy opcjonalny argument overload()definiujący funkcję „domyślną”, która ma być wywoływana, jeśli podpis nie może zostać zidentyfikowany. Argumenty do .add():

  1. fn: functionokreślenie przeciążenia;
  2. a_vArgumentTests: Arrayz functions definiuje testy do uruchomienia na arguments. Każdy functionakceptuje pojedynczy argument i zwraca truetwój na podstawie tego, czy argument jest poprawny;
  3. sAlias(Opcjonalnie): stringzdefiniowanie aliasu w celu uzyskania bezpośredniego dostępu do funkcji przeciążenia ( fn), np. myOverloadedFn.noArgs()Wywoła tę funkcję bezpośrednio, unikając dynamicznych testów polimorfizmu argumentów.

Ta implementacja faktycznie pozwala na więcej niż tylko tradycyjne przeciążenie funkcji, ponieważ drugi a_vArgumentTestsargument .add()w praktyce definiuje typy niestandardowe. Możesz więc przekazywać argumenty nie tylko na podstawie typu, ale także na zakresach, wartościach lub kolekcjach wartości!

Jeśli przejrzysz 145 linii kodu overload(), zobaczysz, że każdy podpis jest podzielony na kategorie według liczby argumentsprzekazanych do niego znaków. Robimy to, aby ograniczyć liczbę uruchomionych testów. Śledzę również liczbę połączeń. Przy pewnym dodatkowym kodzie tablice przeciążonych funkcji mogą zostać ponownie posortowane, aby najpierw przetestować częściej nazywane funkcje, ponownie dodając pewną miarę zwiększenia wydajności.

Teraz są pewne zastrzeżenia ... Ponieważ Javascript jest luźno wpisany, będziesz musiał uważać na swój, vArgumentTestsponieważ integermożna go zweryfikować jako floatitp.

Wersja JSCompress.com (1114 bajtów, 744 bajty skompresowane g):

window.overload=function(){'use strict';function b(n){return'[object Function]'===m.call(n)}function c(n){return!!(n&&n===Object(n))}function d(n){return'[object Array]'===m.call(n)}function e(n){return Array.prototype.slice.call(n)}function g(n,p,q){if(q=d(q)?q:e(q),b(n))return n.apply(p||this,q)}function h(n,p,q){q&&!n[q]&&(n[q]=p)}function k(n){var p=b(n)?{default:n}:c(n)?n:{default:function(){throw'Overload not found for arguments: ['+e(arguments)+']'}},q=function(){var r,s,t,u=arguments,v=p[u.length]||[];for(s=0;s<v.length;s++){for(r=v[s],t=0;t<u.length;t++)if(!v[s].tests[t](u[t])){r=void 0;break}if(r)break}return r?(r.calls++,g(r.fn,this,u)):g(p.default,this,u)};return q.add=function(r,s,t){var u,v=b(r),w=d(s)?s.length:0;if(v)for(u=0;u<w;u++)b(s[u])||(v=_false);if(v)return p[w]=p[w]||[],p[w].push({fn:r,tests:s,calls:0}),h(q,r,t),q;throw'poly.overload: All tests must be functions or strings referencing `is.*`.'},q.list=function(r){return 0<arguments.length?p[r]||[]:p},l.push(q),h(q,p.default,'default'),q}var l=[],m=Object.prototype.toString;return k.is=function(n){return-1<l.indexOf(n)},k}();

2

Możesz teraz przeciążać funkcje w ECMAScript 2018 bez wypełnień, sprawdzać długość / typ zmiennej itp., Wystarczy użyć składni rozkładania .

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"

Co to jest składnia rozprzestrzeniania?

Właściwości Rest / Spread dla propozycji ECMAScript (etap 4) dodaje właściwości spread do literałów obiektowych. Kopiuje własne wyliczalne właściwości z podanego obiektu na nowy obiekt. Więcej na mdn

Uwaga: składnia rozproszona w literałach obiektowych nie działa w Edge i IE i jest funkcją eksperymentalną. zobacz kompatybilność przeglądarki


2

Coś takiego można zrobić w przypadku przeciążenia funkcji.

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

Źródło


1

To stare pytanie, ale myślę, że potrzebuje kolejnego wpisu (choć wątpię, by ktokolwiek go przeczytał). Użycie wyrażeń funkcji natychmiast wywołanych (IIFE) może być używane w połączeniu z zamknięciami i funkcjami wbudowanymi, aby umożliwić przeciążenie funkcji. Rozważ następujący (wymyślony) przykład:

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

Krótko mówiąc, użycie IIFE tworzy zasięg lokalny, co pozwala nam zdefiniować zmienną prywatną olddo przechowywania odniesienia do początkowej definicji funkcji foo. Ta funkcja zwraca następnie funkcję wbudowaną, newFooktóra rejestruje zawartość obu dwóch argumentów, jeśli zostaną przekazane dokładnie dwa argumenty ai blub wywołuje oldfunkcję if arguments.length !== 2. Ten wzorzec można powtarzać dowolną liczbę razy, aby wyposażyć jedną zmienną w kilka różnych definicji funkcjonalnych.


1

Chciałbym podzielić się przydatnym przykładem podejścia podobnego do przeciążonego.

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

Użycie: Clear (); // Usuwa cały dokument

Wyczyść (myDiv); // Czyści panel, do którego odwołuje się myDiv


1

JavaScript jest językiem bez typów i myślę, że sensowne jest przeciążenie metody / funkcji w odniesieniu do liczby parametrów. Dlatego zalecałbym sprawdzenie, czy parametr został zdefiniowany:

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

1
Chcę tylko zauważyć, że bez typu nie oznacza „bez typów”.
hamdiakoguz

1

Od lipca 2017 r. Następującą techniką jest wspólna technika. Pamiętaj, że możemy również sprawdzać typ w ramach funkcji.

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

Dla twojego przypadku użycia, tak bym sobie z tym poradził ES6(ponieważ jest to już koniec 2017 roku):

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

Możesz oczywiście dostosować to do pracy z dowolną ilością parametrów i po prostu odpowiednio zmienić instrukcje warunkowe.


1

w JS nie ma faktycznego przeciążenia, w każdym razie nadal możemy symulować przeciążenie metody na kilka sposobów:

metoda nr 1: użyj obiektu

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

metoda nr 2: użyj parametrów rest (spread)

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

metoda nr 3: użyj niezdefiniowanego

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

metoda nr 4: sprawdzanie typu

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

1

Chociaż parametry domyślne nie są przeciążone, może rozwiązać niektóre problemy, z którymi spotykają się programiści w tym obszarze. Dane wejściowe są ściśle określone przez zamówienie, nie można ponownie zamówić, jak chcesz, jak w klasycznym przeciążeniu:

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

Wyniki w (za rok 2020):

2020
16160
12
65
2021
2023

Więcej informacji: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters


0

Pierwsza opcja naprawdę zasługuje na uwagę, ponieważ wymyśliłem ją w dość złożonej konfiguracji kodu. Tak więc moja odpowiedź brzmi

  1. Używanie przede wszystkim różnych nazw

Z małą, ale niezbędną wskazówką, nazwy powinny wyglądać inaczej dla komputera, ale nie dla ciebie. Nazwa przeciążonych funkcji, takich jak: func, func1, func2.


Chciałem spróbować przeładować, ale postanowiłem po prostu użyć różnych nazw, np. GetDeviceInfoByID i getDeviceInfoByType ...
CramerTV
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.