Varför kan jag inte tilldela om en funktion i TypeScript?


Att tilldela om en funktion i vanlig JavaScript är helt okej kod...

function f() {}

f = function() {}

..., men när jag gör detta med TypeScript får jag ett kompileringsfel som säger att detta inte är tillåtet?! (playground-länk)

function f() {}

f = function () {}; /*
~ Cannot assign to 'f' because it is a function. */

Så jag undrade vad den särskilda anledningen är till att denna funktion verkar vara förbjuden. Jag vet att detta fungerar med funktionsuttryck (se denna playground) men inte med funktionsdeklarationer. Jag kunde inte hitta något hjälpsamt i TypeScripts dokumentation eller någon annanstans.

Observera att jag inte är intresserad av en lösning, utan av en faktisk förklaring till detta beteende.

Svar #1

Förbehåll: Jag vet inte om detta utgör ett objektivt korrekt svar på "varför tillåter inte TypeScript omtilldelningar av funktioner som deklareras via funktionssats?" För att vara säker skulle jag behöva hitta någon auktoritativ dokumentation eller officiellt uttalande som ger motiveringen bakom detta särskilda förbud, och jag har inte hittat någon än. Därför är det som följer bara en kvalificerad gissning med tanke på sådana uttalanden om andra situationer.


Det är inte ett av designmålen för TypeScript att tillåta att all giltig JavaScript kompileras i TypeScript utan att ge kompilatorfel (även om det är ett av designmålen att även med sådana fel ska koden kompileras och genereras som JavaScript). Litanstestet för att ge ett kompilatorfel är inte "kommer detta att orsaka ett runtime-fel i JavaScript" utan "är detta sannolikt ett kodningsmisstag". Se Vad betyder "all giltig JavaScript är giltig TypeScript" och dess svar för en auktoritativ källa.

För exempel på detta, se:

  • microsoft/TypeScript#17203, ett avvisat förslag att låta parseFloat() tillåta icke-string-inmatningar
  • microsoft/TypeScript#22821, ett avvisat förslag att tillåta att anropa split()-metoden på strings utan ett argument
  • microsoft/TypeScript#49766, ett avvisat förslag att tillåta att skicka ett null space-argument till JSON.stringify()-metoden.
  • microsoft/TypeScript#55083, ett avvisat förslag att tillåta spridning av icke-objekt i ett objektliteral.

I alla dessa fall tillåter JavaScript något och det finns ett väldefinierat resultat, men TypeScript anser att det är ett misstag att göra det.

Så det kan vara sant att "att tilldela om en funktion i vanlig JavaScript är helt okej kod", men det innebär inte nödvändigtvis att det borde vara helt okej TypeScript-kod.

Observera att jag inte försöker säga att jag personligen håller med om besluten i alla ovanstående fall eller i detta fall. Det har definitivt funnits tillfällen när jag inte har hållit med TS-teamets beslut om att tillåta eller förbjuda vissa kodningstekniker, och det handlar vanligtvis om en skillnad i prioriteringar för att stödja vissa användningsfall. Om jag tycker att något användningsfall är viktigare än vad TS-teamet tycker, kan jag bli missnöjd när språket inte har en möjlighet för det. Omvänt, om jag tycker att det finns ett stort säkerhetshål i språket men TS-teamet bryr sig om ett användningsfall som hålet möjliggör, kan jag bli missnöjd när språket är osäkert. Dessa kommer i slutändan ner till en kombination av statistik från verklig kod, kompilatorprestanda och de subjektiva åsikterna hos TS-språkets underhållare.


Den sannolika anledningen till att funktioner deklarerade med function-sats inte kan tilldelas på nytt i TypeScript är att TS-teamet tycker att det troligen är ett misstag att göra det.

MDN:s dokumentation för function-satser säger för närvarande "funktionsdeklarationer kan tilldelas om, men du bör undvika att göra det". Jag vet inte om vi kan betrakta MDN som auktoritativt här eller inte, men det är åtminstone en datapunkt.

Allmänt sett skulle det vara olyckligt om något sådant

parseFloat = x => 0

var tillåtna utan fel, eller hur? Om inte TypeScript har infört konceptet med en readonly-funktionsdeklaration och markerat varje enskild inbyggd JS-funktion som en sådan, är vi mest kvar med de två ytterligheterna "tillåt alla funktionsomassigneringar (vars typer matchar)" eller "förhindra alla funktionsomassigneringar", och TS har valt det senare.

När jag tittar på den för stora för GitHubs UI checker.ts-filen, vid ungefär rad 28 031, ser jag

if (assignmentKind) {
  if (!(localOrExportSymbol.flags & SymbolFlags.Variable) &&
    !(isInJSFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule)) {
    const assignmentError = localOrExportSymbol.flags & SymbolFlags.Enum ? Diagnostics.Cannot_assign_to_0_because_it_is_an_enum
      : localOrExportSymbol.flags & SymbolFlags.Class ? Diagnostics.Cannot_assign_to_0_because_it_is_a_class
      : localOrExportSymbol.flags & SymbolFlags.Module ? Diagnostics.Cannot_assign_to_0_because_it_is_a_namespace
      : localOrExportSymbol.flags & SymbolFlags.Function ? Diagnostics.Cannot_assign_to_0_because_it_is_a_function
      : localOrExportSymbol.flags & SymbolFlags.Alias ? Diagnostics.Cannot_assign_to_0_because_it_is_an_import
      : Diagnostics.Cannot_assign_to_0_because_it_is_not_a_variable;

så TypeScript tycker att omfördelning till en function-sats är samma typ av misstag som att omfördela en class-sats eller en module. Det vill säga, det är som ett kategorifel. Du är inte avsedd att skriva över dessa saker.


Så där har du det. Allt jag vet säkert är att det är förbjudet i TypeScript och att i andra fall där TypeScript förbjuder giltig JavaScript är det för att TS-teamet anser att sådan kod sannolikt är ett fel. Det samma gäller förmodligen här, och det finns omständighetsbevis för det.