Grundlegendes zu Klassen in JavaScript

Einführung

JavaScript ist eine prototypbasierte Sprache, und jedes Objekt in JavaScript verfügt über eine versteckte interne Eigenschaft namens[[Prototype]], mit der Objekteigenschaften und -methoden erweitert werden können. Weitere Informationen zu Prototypen finden Sie in unserem Tutorial zuUnderstanding Prototypes and Inheritance in JavaScript.

Bis vor kurzem verwendeten fleißige Entwicklerconstructor functions, um ein objektorientiertes Entwurfsmuster in JavaScript nachzuahmen. Die Sprachspezifikation ECMAScript 2015, oft als ES6 bezeichnet, führte Klassen in die JavaScript-Sprache ein. Klassen in JavaScript bieten keine zusätzliche Funktionalität und werden oft als "syntaktischer Zucker" gegenüber Prototypen und Vererbung bezeichnet, da sie eine klarere und elegantere Syntax bieten. Da andere Programmiersprachen Klassen verwenden, erleichtert die Klassensyntax in JavaScript Entwicklern das Wechseln zwischen Sprachen.

Klassen sind Funktionen

Eine JavaScript-Klasse ist eine Art von Funktion. Klassen werden mit dem Schlüsselwortclassdeklariert. Wir werden die Syntax von Funktionsausdrücken verwenden, um eine Funktion zu initialisieren, und die Syntax von Klassenausdrücken, um eine Klasse zu initialisieren.

// Initializing a function with a function expression
const x = function() {}
// Initializing a class with a class expression
const y = class {}

Mit denObject.getPrototypeOf() method können wir auf die[[Prototype]] eines Objekts zugreifen. Verwenden wir dies, um die leerenfunctionzu testen, die wir erstellt haben.

Object.getPrototypeOf(x);
Outputƒ () { [native code] }

Wir können diese Methode auch für dieclassverwenden, die wir gerade erstellt haben.

Object.getPrototypeOf(y);
Outputƒ () { [native code] }

Der mitfunction undclass deklarierte Code gibt beide eine Funktion[[Prototype]] zurück. Bei Prototypen kann jede Funktion mithilfe des Schlüsselwortsnewzu einer Konstruktorinstanz werden.

const x = function() {}

// Initialize a constructor from a function
const constructorFromFunction = new x();

console.log(constructorFromFunction);
Outputx {}
constructor: ƒ ()

Dies gilt auch für den Unterricht.

const y = class {}

// Initialize a constructor from a class
const constructorFromClass = new y();

console.log(constructorFromClass);
Outputy {}
constructor: class

Diese Beispiele für Prototypkonstruktoren sind ansonsten leer, aber wir können sehen, dass unter der Syntax beide Methoden das gleiche Endergebnis erzielen.

Eine Klasse definieren

Inprototypes and inheritance tutorial haben wir ein Beispiel für die Charaktererstellung in einem textbasierten Rollenspiel erstellt. Fahren wir hier mit diesem Beispiel fort, um die Syntax von Funktionen zu Klassen zu aktualisieren.

Einconstructor function wird mit einer Reihe von Parametern initialisiert, die als Eigenschaften vonthis zugewiesen werden und sich auf die Funktion selbst beziehen. Der erste Buchstabe des Bezeichners wird nach Konvention großgeschrieben.

constructor.js

// Initializing a constructor function
function Hero(name, level) {
    this.name = name;
    this.level = level;
}

Wenn wir dies in die unten gezeigteclass-Syntax übersetzen, sehen wir, dass sie sehr ähnlich aufgebaut ist.

class.js

// Initializing a class definition
class Hero {
    constructor(name, level) {
        this.name = name;
        this.level = level;
    }
}

Wir wissen, dass eine Konstruktorfunktion ein Objektentwurf sein soll, indem der erste Buchstabe des Initialisierers (optional) groß geschrieben wird und die Syntax bekannt ist. Das Schlüsselwortclasskommuniziert das Ziel unserer Funktion auf einfachere Weise.

Der einzige Unterschied in der Syntax der Initialisierung besteht darin, das Schlüsselwortclass anstelle vonfunction zu verwenden und die Eigenschaften innerhalb einerconstructor()-Methode zuzuweisen.

Methoden definieren

Die übliche Praxis bei Konstruktorfunktionen besteht darin, Methoden direkt denprototype zuzuweisen, anstatt in der Initialisierung, wie in der folgenden Methodegreet() gezeigt.

constructor.js

function Hero(name, level) {
    this.name = name;
    this.level = level;
}

// Adding a method to the constructor
Hero.prototype.greet = function() {
    return `${this.name} says hello.`;
}

Mit Klassen wird diese Syntax vereinfacht und die Methode kann direkt zur Klasse hinzugefügt werden. Mit den in ES6 eingeführtenmethod definition shorthand ist das Definieren einer Methode ein noch präziserer Prozess.

class.js

class Hero {
    constructor(name, level) {
        this.name = name;
        this.level = level;
    }

    // Adding a method to the constructor
    greet() {
        return `${this.name} says hello.`;
    }
}

Sehen wir uns diese Eigenschaften und Methoden in Aktion an. Wir werden eine neue Instanz vonHero mit dem Schlüsselwortnew erstellen und einige Werte zuweisen.

const hero1 = new Hero('Varg', 1);

Wenn wir mitconsole.log(hero1) weitere Informationen zu unserem neuen Objekt ausdrucken, sehen wir weitere Details darüber, was mit der Klasseninitialisierung geschieht.

OutputHero {name: "Varg", level: 1}
__proto__:
  ▶ constructor: class Hero
  ▶ greet: ƒ greet()

Wir können in der Ausgabe sehen, dass die Funktionenconstructor() undgreet() auf__proto__ oder[[Prototype]] vonhero1 angewendet wurden und nicht direkt als Methode für dashero1 Objekt. Während dies beim Erstellen von Konstruktorfunktionen klar ist, ist es beim Erstellen von Klassen nicht offensichtlich. Klassen ermöglichen eine einfachere und prägnantere Syntax, opfern dabei jedoch etwas Klarheit.

Klasse erweitern

Konstruktorfunktionen und -klassen zeichnen sich dadurch aus, dass sie auf der Basis des übergeordneten Objekts zu neuen Objektentwürfen erweitert werden können. Dies verhindert die Wiederholung von Code für Objekte, die ähnlich sind, jedoch einige zusätzliche oder spezifischere Funktionen benötigen.

Neue Konstruktorfunktionen können mit der Methodecall() vom übergeordneten Element erstellt werden. Im folgenden Beispiel erstellen wir eine spezifischere Zeichenklasse mit dem NamenMage und weisen ihr die Eigenschaften vonHero mithilfe voncall() zu. Außerdem fügen wir eine zusätzliche Eigenschaft hinzu.

constructor.js

// Creating a new constructor from the parent
function Mage(name, level, spell) {
    // Chain constructor with call
    Hero.call(this, name, level);

    this.spell = spell;
}

Zu diesem Zeitpunkt können wir eine neue Instanz vonMage mit denselben Eigenschaften wieHero sowie eine neue Instanz erstellen, die wir hinzugefügt haben.

const hero2 = new Mage('Lejon', 2, 'Magic Missile');

Wenn Siehero2 an die Konsole senden, können Sie sehen, dass wir basierend auf dem Konstruktor ein neuesMage erstellt haben.

OutputMage {name: "Lejon", level: 2, spell: "Magic Missile"}
__proto__:
    ▶ constructor: ƒ Mage(name, level, spell)

Bei ES6-Klassen wird anstelle voncall das Schlüsselwortsuper verwendet, um auf die übergeordneten Funktionen zuzugreifen. Wir werdenextends verwenden, um auf die übergeordnete Klasse zu verweisen.

class.js

// Creating a new class from the parent
class Mage extends Hero {
    constructor(name, level, spell) {
        // Chain constructor with super
        super(name, level);

        // Add a new property
        this.spell = spell;
    }
}

Jetzt können wir auf die gleiche Weise eine neueMage-Instanz erstellen.

const hero2 = new Mage('Lejon', 2, 'Magic Missile');

Wir werdenhero2 auf die Konsole drucken und die Ausgabe anzeigen.

OutputMage {name: "Lejon", level: 2, spell: "Magic Missile"}
__proto__: Hero
    ▶ constructor: class Mage

Die Ausgabe ist nahezu identisch, mit der Ausnahme, dass in der Klassenkonstruktion[[Prototype]] mit dem übergeordneten Element verknüpft ist, in diesem FallHero.

Im Folgenden finden Sie einen direkten Vergleich des gesamten Prozesses der Initialisierung, des Hinzufügens von Methoden und der Vererbung einer Konstruktorfunktion und einer Klasse.

constructor.js

function Hero(name, level) {
    this.name = name;
    this.level = level;
}

// Adding a method to the constructor
Hero.prototype.greet = function() {
    return `${this.name} says hello.`;
}

// Creating a new constructor from the parent
function Mage(name, level, spell) {
    // Chain constructor with call
    Hero.call(this, name, level);

    this.spell = spell;
}

class.js

// Initializing a class
class Hero {
    constructor(name, level) {
        this.name = name;
        this.level = level;
    }

    // Adding a method to the constructor
    greet() {
        return `${this.name} says hello.`;
    }
}

// Creating a new class from the parent
class Mage extends Hero {
    constructor(name, level, spell) {
        // Chain constructor with super
        super(name, level);

        // Add a new property
        this.spell = spell;
    }
}

Obwohl die Syntax sehr unterschiedlich ist, ist das zugrunde liegende Ergebnis zwischen beiden Methoden nahezu gleich. Klassen ermöglichen eine präzisere Erstellung von Objektentwürfen, und Konstruktorfunktionen beschreiben genauer, was unter der Haube geschieht.

Fazit

In diesem Tutorial haben wir die Ähnlichkeiten und Unterschiede zwischen JavaScript-Konstruktorfunktionen und ES6-Klassen kennengelernt. Sowohl Klassen als auch Konstruktoren imitieren ein objektorientiertes Vererbungsmodell für JavaScript, eine prototypbasierte Vererbungssprache.

Das Verständnis der prototypischen Vererbung ist von größter Bedeutung, um ein effektiver JavaScript-Entwickler zu sein. Das Kennenlernen von Klassen ist äußerst hilfreich, da beliebte JavaScript-Bibliotheken wieReacthäufig die Syntax vonclassverwenden.