Przeciążenie funkcji TypeScript

243

Sekcja 6.3 specyfikacji języka TypeScript mówi o przeciążeniu funkcji i podaje konkretne przykłady, jak to zaimplementować. Jeśli jednak spróbuję czegoś takiego:

export class LayerFactory { 

    constructor (public styleFactory: Symbology.StyleFactory) { }

    createFeatureLayer (userContext : Model.UserContext, mapWrapperObj : MapWrapperBase) : any {           
         throw "not implemented";
    }                 

    createFeatureLayer(layerName : string, style : any) : any {
        throw "not implemented";
     }        

}

Otrzymuję błąd kompilatora wskazujący na duplikat identyfikatora, mimo że parametry funkcji są różnych typów. Nawet jeśli dodam dodatkowy parametr do drugiej funkcji createFeatureLayer, nadal pojawia się błąd kompilatora. Proszę o pomysły.

Klaus Nji
źródło
Możliwy duplikat przeciążenia metody?
BuZZ-dEE

Odpowiedzi:

189

Może to być spowodowane tym, że gdy obie funkcje są skompilowane w JavaScript, ich podpis jest całkowicie identyczny. Ponieważ JavaScript nie ma typów, w efekcie tworzymy dwie funkcje, biorąc tę ​​samą liczbę argumentów. Tak więc TypeScript ogranicza nas do tworzenia takich funkcji.

TypeScript obsługuje przeciążanie w oparciu o liczbę parametrów, ale kroki, które należy wykonać, są nieco inne, jeśli porównamy je z językami OO. W odpowiedzi na inne pytanie SO ktoś wyjaśnił to dobrym przykładem: Przeciążenie metody? .

Zasadniczo tworzymy tylko jedną funkcję i szereg deklaracji, aby TypeScript nie dawał błędów kompilacji. Po skompilowaniu tego kodu do JavaScript widoczna będzie tylko konkretna funkcja. Ponieważ funkcja JavaScript może być wywołana przez przekazanie wielu argumentów, po prostu działa.

S. Ravi Kiran
źródło
50
Język może zostać zmieniony w celu wsparcia tego. Teoretycznie można wygenerować implementacje funkcji, które są nazywane osobno i wywoływane przez skompilowany TypeScript (np. CreateFeatureLayer_1 i createFeatureLayer_2), a createFeatureLayer może następnie określić, które z nich wywołać na podstawie treści argumentów dotyczących współpracy z waniliowym JavaScript.
Thomas S. Trias
8
Mówisz to tak, jakby przeciążenie w TypeScript było możliwe tylko na podstawie liczby parametrów, podczas gdy przeciążenie na podstawie typu jest również możliwe, jak pokazano w odpowiedzi Steve'a Fentona.
Matthijs Wessels
9
To jest trochę kiepskie; TypeScript powinien naprawdę generować „funkcję meta”, która odpowiednio wybiera implementację o unikatowej nazwie na podstawie tego, co została przekazana. Jak to jest teraz, istnieje szczelina, w której można przekazać kompilator, ale implementacja wykrywania typu może być niepoprawna.
Ezekiel Victor
5
@EzekielVictor TypeScript zrobiłby to, gdyby istniał niezawodny sposób sprawdzania typów w czasie wykonywania.
thorn̈
3
Jest to nawet bardziej skomplikowane, jest możliwe do wykonania z typami JavaScript, ale pojęcia specyficzne dla TS, takie jak interfejsy, types, wyliczenia, generyczne itp., Giną w czasie wykonywania. Dlatego też nie możesz tego zrobić someObject instanceof ISomeInterfaceDefinedInTypeScript.
Morgan Touverey Quilling
209

Kiedy przeciążasz się w TypeScript, masz tylko jedną implementację z wieloma podpisami.

class Foo {
    myMethod(a: string);
    myMethod(a: number);
    myMethod(a: number, b: string);
    myMethod(a: any, b?: string) {
        alert(a.toString());
    }
}

Tylko trzy przeciążenia są rozpoznawane przez TypeScript jako możliwe sygnatury wywołania metody, a nie rzeczywista implementacja.

W twoim przypadku osobiście użyłbym dwóch metod o różnych nazwach, ponieważ parametry są niewystarczające, co sprawia, że ​​ciało metody będzie musiało mieć wiele „ifs”, aby zdecydować, co robić.

TypeScript 1.4

Począwszy od TypeScript 1.4, zazwyczaj można usunąć potrzebę przeciążenia za pomocą typu unii. Powyższy przykład można lepiej wyrazić za pomocą:

myMethod(a: string | number, b?: string) {
    alert(a.toString());
}

Typ ato „albo stringalbo number”.

Fenton
źródło
Świetna odpowiedź. Chciałbym tylko podkreślić, że może to nie być pomocne, gdy ktoś próbuje przeciążać z następujących powodów: Chciałbym mieć instancję, w której za pomocą tego samego konstruktora mogę przekazać obiekt definiujący wszystkie oczekiwane właściwości i w jednym przypadku class Foo { constructor(obj) { } constructor (a: number, b: string, c: boolean) {} }
podaj
Ogólnie rzecz biorąc, wolałbym użyć metody fabryki stworzyć mi obiektowi każdy sposób - nie ma potrzeby oddziału, jeżeli zadzwonisz Foo.fromObject(obj)a Foo.fromJson(str)i tak dalej.
Fenton
Ale to postuluje, że zawsze przekaże się parametry jako obiekt lub pojedynczy ciąg, co jeśli chcę, aby były przekazywane osobno, jak podkreślono w poprzednim komentarzu? Foo.methos(1, 2, 3) Foo.method(1) Foo.method(Obj) Zauważyłem również, że masz różne metody w Fooklasie, fromObject i fromJson?
Hlawuleka MAS
1
Jeśli podążysz za tą różnicą z powrotem do źródła, zwykle okaże się, że nie ma takiej potrzeby. Na przykład musisz wpisać myNumlub myObjtak, więc dlaczego nie mieć osobnych metod i wszystko wyjaśnić / uniknąć niepotrzebnej logiki rozgałęziania.
Fenton
2
Zauważ, że użycie typu unii może być problematyczne, jeśli chcesz mieć różne typy zwrotów w zależności od parametrów. Można to rozwiązać za pomocą ogólnych, jeśli typ zwracany jest zawsze zgodny z jednym z typów parametrów, ale w innych przypadkach przeciążenia są najlepszym rozwiązaniem.
John Montgomery,
45

Możesz zadeklarować przeciążoną funkcję, deklarując, że ma ona typ, który ma wiele sygnatur wywołania:

interface IFoo
{
    bar: {
        (s: string): number;
        (n: number): string;
    }
}

Następnie:

var foo1: IFoo = ...;

var n: number = foo1.bar('baz');     // OK
var s: string = foo1.bar(123);       // OK
var a: number[] = foo1.bar([1,2,3]); // ERROR

Rzeczywista definicja funkcji musi być pojedyncza i odpowiednio wykonywać wewnętrzną dyspozycję na podstawie jej argumentów.

Na przykład za pomocą klasy (która może zaimplementować IFoo, ale nie musi):

class Foo
{
    public bar(s: string): number;
    public bar(n: number): string;
    public bar(arg: any): any 
    {
        if (typeof(arg) === 'number')
            return arg.toString();
        if (typeof(arg) === 'string')
            return arg.length;
    }
}

Interesujące jest to, że anyformularz jest ukryty przez bardziej szczegółowo nadpisane zmiany.

var foo2: new Foo();

var n: number = foo2.bar('baz');     // OK
var s: string = foo2.bar(123);       // OK
var a: number[] = foo2.bar([1,2,3]); // ERROR
Drew Noakes
źródło
1

Co to jest przeciążenie funkcji w ogóle?

Przeciążenie funkcji lub przeciążenie metody to możliwość tworzenia wielu funkcji o tej samej nazwie z różnymi implementacjami ( Wikipedia )


Co to jest przeciążenie funkcji w JS?

Ta funkcja nie jest możliwa w JS - ostatnia zdefiniowana funkcja jest przyjmowana w przypadku wielu deklaracji:

function foo(a1, a2) { return `${a1}, ${a2}` }
function foo(a1) { return `${a1}` } // replaces above `foo` declaration
foo(42, "foo") // "42"

... i w TS?

Przeciążenia to konstrukcja czasu kompilacji, która nie ma wpływu na środowisko wykonawcze JS:

function foo(s: string): string // overload #1 of foo
function foo(s: string, n: number): number // overload #2 of foo
function foo(s: string, n?: number): string | number {/* ... */} // foo implementation

W przypadku użycia powyższego kodu (bezpieczniejszego niż JS) wyzwalany jest duplikat błędu implementacji. TS wybiera pierwsze dopasowanie przeciążenia w kolejności od góry, więc przeciążenia są sortowane od najbardziej specyficznych do najbardziej rozległych.


Przeciążenie metody w TS: bardziej złożony przykład

Typy metod klas przeciążonych mogą być używane w podobny sposób jak przeciążanie funkcji:

class LayerFactory {
    createFeatureLayer(a1: string, a2: number): string
    createFeatureLayer(a1: number, a2: boolean, a3: string): number
    createFeatureLayer(a1: string | number, a2: number | boolean, a3?: string)
        : number | string { /*... your implementation*/ }
}

const fact = new LayerFactory()
fact.createFeatureLayer("foo", 42) // string
fact.createFeatureLayer(3, true, "bar") // number

Możliwe są bardzo różne przeciążenia, ponieważ implementacja funkcji jest kompatybilna ze wszystkimi sygnaturami przeciążenia - wymuszonymi przez kompilator.

Więcej informacji:

ford04
źródło
0

Jako head-up do innych, zauważyłem, że przynajmniej tak jak manifestuje się TypeScript skompilowany przez WebPack dla Angulara 2, po cichu dostajesz overWRITTEN zamiast przepełnionych metod.

myComponent {
  method(): { console.info("no args"); },
  method(arg): { console.info("with arg"); }
}

Powołanie:

myComponent.method()

wydaje się wykonywać metodę z argumentami, dyskretnie ignorując wersję bez argonu, z wyjściem:

with arg
mtyson
źródło
2
Nie możesz zadeklarować oddzielnych ciał dla swoich przeciążeń, tylko inne podpisy.
adharris
5
Nie jestem pewien, której wersji kompilatora TypeScript używasz, ale bieżąca wersja wyświetla Duplicate function implementationostrzeżenie dla takiego kodu.
Royston Shufflebotham
0

Przeciążenie funkcji w maszynopisie:

Według Wikipedii (i wielu książek o programowaniu) definicja przeciążenia metody / funkcji jest następująca:

W niektórych językach programowania przeciążenie funkcji lub przeciążenie metody to możliwość tworzenia wielu funkcji 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.

W maszynopisie nie możemy mieć różnych implementacji tej samej funkcji, które są wywoływane zgodnie z liczbą i typem argumentów. Wynika to z faktu, że po kompilacji TS do JS funkcje w JS mają następujące cechy:

  • Definicje funkcji JavaScript nie określają typów danych dla swoich parametrów
  • Funkcje JavaScript nie sprawdzają liczby argumentów po wywołaniu

Dlatego w ścisłym znaczeniu można argumentować, że przeciążenie funkcji TS nie istnieje. Są jednak rzeczy, które możesz zrobić w kodzie TS, które mogą doskonale naśladować przeciążenie funkcji.

Oto przykład:

function add(a: number, b: number, c: number): number;
function add(a: number, b: number): any;
function add(a: string, b: string): any;

function add(a: any, b: any, c?: any): any {
  if (c) {
    return a + c;
  }
  if (typeof a === 'string') {
    return `a is ${a}, b is ${b}`;
  } else {
    return a + b;
  }
}

Dokumenty TS nazywają tę metodę przeciążeniem, a my w zasadzie dostarczyliśmy wiele sygnatur metod (opisy możliwych parametrów i typów) do kompilatora TS. Teraz TS może dowiedzieć się, czy poprawnie wywołaliśmy naszą funkcję w czasie kompilacji, a jeśli wystąpiliśmy niepoprawnie, wywołaliśmy błąd.

Willem van der Veen
źródło