Wie schreibe ich eine Erweiterungsmethode in JavaScript?

83

Ich muss ein paar Erweiterungsmethoden in JS schreiben. Ich weiß genau, wie man das in C # macht. Beispiel:

public static string SayHi(this Object name)
{
    return "Hi " + name + "!";
}

und dann angerufen von:

string firstName = "Bob";
string hi = firstName.SayHi();

Wie würde ich so etwas in JavaScript machen?

Matt Cashatt
quelle

Antworten:

149

JavaScript hat kein genaues Analogon für die Erweiterungsmethoden von C #. JavaScript und C # sind ganz unterschiedliche Sprachen.

Das nächste ähnliche Element ist das Ändern des Prototypobjekts aller Zeichenfolgenobjekte : String.prototype. Im Allgemeinen wird empfohlen, die Prototypen integrierter Objekte im Bibliothekscode nicht zu ändern, um sie mit anderem Code zu kombinieren, den Sie nicht steuern. (Dies in einer Anwendung zu tun, in der Sie steuern, welcher andere Code in der Anwendung enthalten ist, ist in Ordnung.)

Wenn Sie tun , den Prototyp eine integrierten in ändern, ist es am besten (bei weitem) , dass eine machen , nicht-zählbare Eigenschaft mithilfe Object.defineProperty(ES5 +, so dass im Grunde jeder modernen JavaScript - Umgebung und nicht IE8¹ oder früher). Um der Aufzählbarkeit, Beschreibbarkeit und Konfigurierbarkeit anderer Zeichenfolgenmethoden zu entsprechen, sieht es folgendermaßen aus:

Object.defineProperty(String.prototype, "SayHi", {
    value: function SayHi() {
        return "Hi " + this + "!";
    },
    writable: true,
    configurable: true
});

(Die Standardeinstellung für enumerableist false.)

Wenn Sie veraltete Umgebungen unterstützen müssen, String.prototypekönnen Sie insbesondere möglicherweise eine aufzählbare Eigenschaft erstellen:

// Don't do this if you can use `Object.defineProperty`
String.prototype.SayHi = function SayHi() {
    return "Hi " + this + "!";
};

Das ist keine gute Idee, aber Sie könnten damit durchkommen. Mach das niemals mit Array.prototypeoder Object.prototype; Das Erstellen von unzähligen Eigenschaften auf diesen ist eine schlechte Sache.

Einzelheiten:

JavaScript ist eine prototypische Sprache. Das bedeutet, dass jedes Objekt von einem Prototypobjekt unterstützt wird . In JavaScript wird dieser Prototyp auf vier Arten zugewiesen:

  • Durch die Konstruktorfunktion für das Objekt (z. B. new Fooerstellt ein Objekt mit Foo.prototypeals Prototyp)
  • Durch die Object.createin ES5 (2009) hinzugefügte Funktion
  • Durch die __proto__Accessor-Eigenschaft (ES2015 +, nur in Webbrowsern, in einigen Umgebungen vorhanden, bevor sie standardisiert wurde) oder Object.setPrototypeOf(ES2015 +)
  • Von der JavaScript-Engine beim Erstellen eines Objekts für ein Grundelement, weil Sie eine Methode dafür aufrufen (dies wird manchmal als "Promotion" bezeichnet).

Da es sich in Ihrem Beispiel firstNameum ein Zeichenfolgenprimitiv handelt, wird es bei Stringjedem Aufruf einer Methode zu einer Instanz heraufgestuft, und Stringder Prototyp dieser Instanz ist String.prototype. Wenn Sie also eine Eigenschaft hinzufügen, String.prototypedie auf Ihre SayHiFunktion verweist , ist diese Funktion für alle StringInstanzen verfügbar (und effektiv für Zeichenfolgenprimitive, da diese heraufgestuft werden).

Beispiel:

Es gibt einige wesentliche Unterschiede zwischen dieser und der C # -Erweiterungsmethode:

  • (Wie DougR in einem Kommentar hervorhob ) Die Erweiterungsmethoden von C # können für nullReferenzen aufgerufen werden . Wenn Sie eine stringErweiterungsmethode haben, lautet dieser Code:

    string s = null;
    s.YourExtensionMethod();
    

    funktioniert. Das stimmt nicht mit JavaScript. nullist ein eigener Typ, und jede Eigenschaftsreferenz auf nulllöst einen Fehler aus. (Und selbst wenn dies nicht der Fall wäre, gibt es keinen Prototyp, der für den Null-Typ erweitert werden könnte.)

  • (Wie ChrisW in einem Kommentar betonte ) Die Erweiterungsmethoden von C # sind nicht global. Sie sind nur zugänglich, wenn der Namespace, in dem sie definiert sind, vom Code mithilfe der Erweiterungsmethode verwendet wird. (Sie sind wirklich syntaktischer Zucker für statische Aufrufe, weshalb sie funktionieren null.) Das trifft in JavaScript nicht zu: Wenn Sie den Prototyp eines integrierten Systems ändern, wird diese Änderung von allen Codes im gesamten Bereich gesehen , in dem Sie sich befinden Tun Sie dies in (ein Bereich ist die globale Umgebung und die zugehörigen intrinsischen Objekte usw.). Wenn Sie dies auf einer Webseite tun, wird der gesamte Code, den Sie auf dieser Seite laden, geändert. Wenn Sie dies in einem Node.js-Modul tun, alleCode, der im selben Bereich wie dieses Modul geladen wurde, sieht die Änderung. In beiden Fällen tun Sie dies deshalb nicht im Bibliothekscode. (Web-Worker und Node.js Worker-Threads werden in ihrem eigenen Bereich geladen, sodass sie eine andere globale Umgebung und andere Eigenschaften als der Haupt-Thread haben. Dieser Bereich wird jedoch weiterhin mit allen von ihnen geladenen Modulen geteilt .)


¹ IE8 hat zwar Object.defineProperty, funktioniert aber nur mit DOM-Objekten, nicht mit JavaScript-Objekten. String.prototypeist ein JavaScript-Objekt.

TJ Crowder
quelle
@DougR: Entschuldigung, Standardkommentarbereinigung. Wenn ein Kommentar veraltet ist, können Mods oder Power-User ihn entfernen (Mods können dies direkt tun, Power-User müssen sich in der Überprüfungswarteschlange zusammenschließen). Ich habe die Antwort aktualisiert, um darauf hinzuweisen, dass Sie dies gemeldet haben. :-)
TJ Crowder
1
@Grundy: Danke, ja, der springende Punkt des String s = null;Teils war zu verwenden s.YourExtensionMethod()! Schätzen Sie den Fang. :-)
TJ Crowder
Vielen Dank, dass Sie hervorgehoben haben, dass die Erweiterungsmethode für Null-Entitäten funktioniert.
Ram
1
Wenn Sie dies beispielsweise in Node.js tun, wirkt sich dies vermutlich auf jede Zeichenfolge im Programm aus (einschließlich der neuen Eigenschaft) - einschließlich anderer Module? Würden zwei Module in Konflikt geraten, wenn beide eine "SayHi" -Eigenschaft definieren?
ChrisW
1
@ChrisW - Ganz. :-) Stattdessen können Sie eine Array-Unterklasse ( class MyArray extends Array { singleOrDefault() { ... }}) erstellen. Dies bedeutet jedoch, dass Sie dies MyArray.from(originalArray)vor der Verwendung tun müssen , wenn originalArrayes sich um ein langweiliges altes Array handelt. Es gibt auch LINQ-ähnliche Bibliotheken für JavaScript ( hier eine Zusammenfassung ), bei denen ebenfalls ein Linq-Objekt aus dem Array erstellt wird, bevor Dinge ausgeführt werden (nun, LINQ zu JavaScript hat es getan, ich habe keine anderen verwendet [und dieses nicht verwendet) in Jahren]). (Übrigens, aktualisierte die Antwort, danke.)
TJ Crowder
0

Verwenden der globalen Erweiterung

declare global {
    interface DOMRect {
        contains(x:number,y:number): boolean;
    }
}
/* Adds contain method to class DOMRect */
DOMRect.prototype.contains = function (x:number, y:number) {                    
    if (x >= this.left && x <= this.right &&
        y >= this.top && y <= this.bottom) {
        return true
    }
    return false
};
Saleh
quelle
Funktioniert dies mit reinem / Vanille-Javascript oder muss ich herausfinden, wie man Typoskript verwendet? Ich frage, da Sie auf
typescriptlang.org