Tutorial

Grundlegendes zu Klassen in JavaScript

DevelopmentJavaScript

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 über Prototypen finden Sie in unserem Tutorial Prototypen und Vererbung in JavaScript verstehen.

Bis vor kurzem verwendeten fleißige Entwickler Konstruktorfunktionen, 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ätzlichen Funktionen und werden häufig 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üsselwort class deklariert. 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 {}

Mithilfe der Methode Object.getPrototypeOf() können wir auf den [[Prototype]] eines Objekts zugreifen. Damit werden wir jetzt die von uns erstellte leere Funktion testen.

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

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

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

Der mit function und class deklarierte Code gibt eine Funktion [[Prototype]] zurück. Bei Prototypen kann jede Funktion durch Verwendung des Schlüsselworts new zu einer Konstruktorinstanz werden.

const x = function() {}

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

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

Dies gilt auch für Klassen.

const y = class {}

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

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

Diese Prototyp-Konstruktorbeispiele sind ansonsten leer, aber wir können sehen, wie unter der Syntax beide Methoden das gleiche Endergebnis erzielen.

Definieren einer Klasse

In dem Tutorial Prototypen und Vererbung haben wir ein Beispiel erstellt, das auf der Charaktererstellung in einem textbasierten Rollenspiel basiert. Fahren wir hier mit diesem Beispiel fort, um die Syntax von Funktionen auf Klassen zu aktualisieren.

Eine Konstruktorfunktion wird mit einer Reihe von Parametern initialisiert, die als Eigenschaften von this zugewiesen werden, was die Referenz auf die Funktion selbst darstellt. Der erste Buchstabe des Bezeichners wird entsprechend der 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 dargestellte class-Syntax übersetzen, sehen wir, dass sie sehr ähnlich strukturiert ist.

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

Anhand der Großschreibung des Anfangsbuchstabens des Initialisierers (optional) und durch die Kenntnis der Syntax wissen wir, dass eine Konstruktorfunktion ein Objektentwurf sein soll. Das Schlüsselwort class kommuniziert auf einfachere Weise das Ziel unserer Funktion.

Der einzige Unterschied in der Syntax der Initialisierung besteht darin, das Schlüsselwort class anstatt von function zu verwenden und die Eigenschaften in einer constructor()-Methode zuzuweisen.

Definieren von Methoden

Die übliche Vorgehensweise bei Konstruktorfunktionen besteht darin, Methoden anstatt in der Initialisierung direkt dem prototype zuzuweisen, wie in der greet ()-Methode unten dargestellt.

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. Unter Verwendung der in ES6 eingeführten Kurzform method definition 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 erstellen eine neue Instanz von Hero mit dem Schlüsselwort new und weisen einige Werte zu.

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

Wenn wir mit console.log (hero1) mehr Informationen über unser neues Objekt ausdrucken, können wir mehr Details dazu sehen, was mit der Klasseninitialisierung passiert.

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

Aus der Ausgabe wird ersichtlich, dass die Funktionen constructor() und greet() auf __proto__ oder [[Prototype] von hero1 angewendet wurden, nicht direkt als Methode für das Objekt hero1. 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; sie büßen jedoch an Klarheit ein.

Erweitern einer Klasse

Ein Vorteil von Konstruktorfunktionen und -klassen besteht darin, 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 vom übergeordneten Element mit der Methode call() erstellt werden. Im nachstehenden Beispiel erstellen wir eine spezifischere Charakterklasse namens Mage und weisen ihr die Eigenschaften von Hero zu, indem wir call () verwenden. 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 von Mage erstellen, die dieselben Eigenschaften wie Hero und die von uns neu hinzugefügte Eigenschaft verwendet.

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

Wenn wir hero2 an die Konsole senden, können wir sehen, dass wir einen neuen Mage erstellt haben, der auf dem Konstruktor basiert.

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

Bei ES6-Klassen wird das Schlüsselwort super anstelle von call verwendet, um auf die übergeordneten Funktionen zuzugreifen. Wir werden extends 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 in derselben Weise eine neue Mage-Instanz erstellen.

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

Wir drucken hero2 auf der Konsole und zeigen die Ausgabe an.

Output
Mage {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 Fall Hero.

Nachstehend 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 aussieht, ist das zugrundeliegende Ergebnis in beiden Methoden nahezu identisch. Klassen ermöglichen eine präzisere Erstellung von Objektentwürfen, und Konstruktorfunktionen beschreiben genauer, was unter der Haube geschieht.

Zusammenfassung

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, damit ein JavaScript-Entwickler effektiv arbeiten kann. Ein Verständnis von Klassen ist äußerst hilfreich, da gängige JavaScript-Bibliotheken wie React häufig die Syntax class verwenden.

0 Comments

Creative Commons License