Suche…


Syntax

  • Klasse Foo {}
  • Klasse Foo erweitert Bar {}
  • Klasse Foo {Konstruktor () {}}
  • Klasse Foo {myMethod () {}}
  • Klasse Foo {get myProperty () {}}
  • Klasse Foo {set myProperty (newValue) {}}
  • Klasse Foo {statisch myStaticMethod () {}}
  • Klasse Foo {static get myStaticProperty () {}}
  • const Foo = Klasse Foo {};
  • const Foo = Klasse {};

Bemerkungen

class wurde nur im Rahmen des Standards von 2015 zu JavaScript hinzugefügt.

Javascript-Klassen sind syntaktischer Zucker über die bereits auf Prototypen basierende Vererbung von JavaScript. Diese neue Syntax führt kein neues objektorientiertes Vererbungsmodell in JavaScript ein, sondern nur eine einfachere Methode zum Umgang mit Objekten und Vererbung. Eine class ist im Wesentlichen eine Abkürzung für manuell einen Konstruktor definiert function und Hinzufügen von Eigenschaften zu dem Prototyp des Konstruktors. Ein wichtiger Unterschied besteht darin, dass Funktionen direkt (ohne das new Schlüsselwort) aufgerufen werden können, während eine Klasse, die direkt aufgerufen wird, eine Ausnahme auslöst.

class someClass {
    constructor () {}
    someMethod () {}
}
 
console.log(typeof someClass);               
console.log(someClass);
console.log(someClass === someClass.prototype.constructor);                         
console.log(someClass.prototype.someMethod);
 
// Output:
// function
// function someClass() { "use strict"; }
// true
// function () { "use strict"; }

Wenn Sie eine frühere Version von JavaScript verwenden, benötigen Sie einen Transpiler wie oder einen , um den Code in eine Version zu kompilieren, die die Zielplattform verstehen kann.

Klassenkonstruktor

Der grundlegende Teil der meisten Klassen ist der Konstruktor, der den Anfangszustand der einzelnen Instanzen festlegt und alle Parameter behandelt, die beim Aufruf von new .

Es ist in einem class definiert, als ob Sie eine Methode mit dem Namen constructor , obwohl es tatsächlich als Sonderfall behandelt wird.

class MyClass {
    constructor(option) {
        console.log(`Creating instance using ${option} option.`);
        this.option = option;
    }
}

Verwendungsbeispiel:

const foo = new MyClass('speedy'); // logs: "Creating instance using speedy option"

Eine kleine Bemerkung ist, dass ein Klassenkonstruktor nicht mit dem Schlüsselwort static static , wie dies für andere Methoden beschrieben wird.

Statische Methoden

Statische Methoden und Eigenschaften werden in der Klasse / im Konstruktor selbst definiert , nicht in Instanzobjekten. Diese werden in einer Klassendefinition mit dem static Schlüsselwort angegeben.

class MyClass {
    static myStaticMethod() {
        return 'Hello';
    }

    static get myStaticProperty() {
        return 'Goodbye';
    }
}

console.log(MyClass.myStaticMethod()); // logs: "Hello"
console.log(MyClass.myStaticProperty); // logs: "Goodbye"

Wir können sehen, dass statische Eigenschaften für Objektinstanzen nicht definiert sind:

const myClassInstance = new MyClass();

console.log(myClassInstance.myStaticProperty); // logs: undefined

Sie sind jedoch in Unterklassen definiert:

class MySubClass extends MyClass {};

console.log(MySubClass.myStaticMethod()); // logs: "Hello"
console.log(MySubClass.myStaticProperty); // logs: "Goodbye"

Getter und Setter

Mit Getern und Setters können Sie ein benutzerdefiniertes Verhalten zum Lesen und Schreiben einer bestimmten Eigenschaft in Ihrer Klasse definieren. Für den Benutzer sehen sie genauso aus wie jede typische Eigenschaft. Intern wird jedoch eine von Ihnen bereitgestellte benutzerdefinierte Funktion verwendet, um den Wert beim Zugriff auf die Eigenschaft (den Getter) zu bestimmen und alle erforderlichen Änderungen vorzunehmen, wenn die Eigenschaft zugewiesen wird (der Setter).

In einer class wird ein Getter wie eine Methode ohne Argumente geschrieben, der get Schlüsselwort get vorangestellt ist. Ein Setter ist ähnlich, mit der Ausnahme, dass er ein Argument akzeptiert (der neue Wert wird zugewiesen) und stattdessen das set Schlüsselwort verwendet wird.

Hier ist eine Beispielklasse, die einen Getter und Setter für die .name bereitstellt. Bei jeder Zuweisung zeichnen wir den neuen Namen in einem internen Array .names_ . Bei jedem Zugriff wird der neueste Name zurückgegeben.

class MyClass {
    constructor() {
        this.names_ = [];
    }

    set name(value) {
        this.names_.push(value);
    }

    get name() {
        return this.names_[this.names_.length - 1];
    }
}

const myClassInstance = new MyClass();
myClassInstance.name = 'Joe';
myClassInstance.name = 'Bob';

console.log(myClassInstance.name); // logs: "Bob"
console.log(myClassInstance.names_); // logs: ["Joe", "Bob"]

Wenn Sie nur einen Setter definieren, wird beim Versuch, auf die Eigenschaft zuzugreifen, immer undefined .

const classInstance = new class {
    set prop(value) {
        console.log('setting', value);
    }
};

classInstance.prop = 10; // logs: "setting", 10

console.log(classInstance.prop); // logs: undefined

Wenn Sie nur einen Getter definieren, hat der Versuch, die Eigenschaft zuzuweisen, keine Auswirkungen.

const classInstance = new class {
    get prop() {
        return 5;
    }
};

classInstance.prop = 10;

console.log(classInstance.prop); // logs: 5

Klassenvererbung

Vererbung funktioniert genauso wie in anderen objektorientierten Sprachen: In der übergeordneten Klasse definierte Methoden sind in der erweiterten Unterklasse zugänglich.

Wenn die Unterklasse einen eigenen Konstruktor deklariert , dann muss es den Eltern Konstruktor über aufrufen super() , bevor er Zugriff auf this .

class SuperClass {

    constructor() {
        this.logger = console.log;
    }

    log() {
        this.logger(`Hello ${this.name}`);
    }

}

class SubClass extends SuperClass {

    constructor() {
        super();
        this.name = 'subclass';
    }

}

const subClass = new SubClass();

subClass.log(); // logs: "Hello subclass"

Private Mitglieder

JavaScript unterstützt private Mitglieder technisch nicht als Sprachfunktion. Privacy - beschrieben von Douglas Crockford - wird stattdessen über Closures (beibehaltener Funktionsumfang) emuliert, die bei jedem Instantiierungsaufruf einer Konstruktorfunktion generiert werden.

Das Queue zeigt, wie mit Konstruktorfunktionen der lokale Status beibehalten und auch über privilegierte Methoden zugänglich gemacht werden kann.

class Queue {

  constructor () {                    // - does generate a closure with each instantiation.

    const list = [];                  // - local state ("private member").

    this.enqueue = function (type) {  // - privileged public method
                                      //   accessing the local state
      list.push(type);                //   "writing" alike.
      return type;
    };
    this.dequeue = function () {      // - privileged public method
                                      //   accessing the local state
      return list.shift();            //   "reading / writing" alike.
    };
  }
}


var q = new Queue;            //
                              //
q.enqueue(9);                 // ... first in ...
q.enqueue(8);                 //
q.enqueue(7);                 //
                              //
console.log(q.dequeue());     // 9 ... first out.
console.log(q.dequeue());     // 8
console.log(q.dequeue());     // 7
console.log(q);               // {}
console.log(Object.keys(q));  // ["enqueue","dequeue"]

Bei jeder Instantiierung eines Queue Typs generiert der Konstruktor eine Schließung.

Daher haben sowohl die eigenen Methoden des Queue Typs enqueue als auch dequeue (siehe Object.keys(q) ) immer noch Zugriff auf eine list , die weiterhin in ihrem einschließenden Bereich lebt , der zum Bauzeitpunkt beibehalten wurde.

Wenn Sie dieses Muster verwenden, indem Sie private Mitglieder über privilegierte öffentliche Methoden emulieren, sollte bedacht werden, dass mit jeder Instanz zusätzlicher Speicher für jede eigene Eigenschaftsmethode verbraucht wird (da dies Code ist, der nicht freigegeben / wiederverwendet werden kann). Dasselbe gilt für die Menge / Größe des Zustands, der in einem solchen Abschluss gespeichert wird.

Dynamische Methodennamen

Es gibt auch die Möglichkeit, Ausdrücke auszuwerten, wenn Methoden benannt werden, ähnlich wie Sie mit [] auf die Objekteigenschaften zugreifen können. Dies kann für dynamische Eigenschaftsnamen hilfreich sein, wird jedoch häufig in Verbindung mit Symbolen verwendet.

let METADATA = Symbol('metadata');

class Car {
  constructor(make, model) {
    this.make = make;
    this.model = model;
  }
  
  // example using symbols
  [METADATA]() {
    return {
      make: this.make,
      model: this.model
    };
  }

  // you can also use any javascript expression

  // this one is just a string, and could also be defined with simply add()
  ["add"](a, b) {
    return a + b;
  }

  // this one is dynamically evaluated
  [1 + 2]() {
    return "three";
  }
}

let MazdaMPV = new Car("Mazda", "MPV");
MazdaMPV.add(4, 5); // 9
MazdaMPV[3](); // "three"
MazdaMPV[METADATA](); // { make: "Mazda", model: "MPV" }

Methoden

Methoden können in Klassen definiert werden, um eine Funktion auszuführen und optional ein Ergebnis zurückzugeben.
Sie können Argumente vom Anrufer erhalten.

class Something {
    constructor(data) {
        this.data = data
    }

    doSomething(text) {
        return {
            data: this.data,
            text
        }
    }
}

var s = new Something({})
s.doSomething("hi") // returns: { data: {}, text: "hi" }

Verwalten von privaten Daten mit Klassen

Eines der häufigsten Hindernisse bei der Verwendung von Klassen ist der richtige Umgang mit privaten Staaten. Es gibt vier gängige Lösungen für den Umgang mit Privatstaaten:

Symbole verwenden

Symbole sind ein neuer primitiver Typ, der in ES2015 eingeführt wurde, wie in MDN definiert

Ein Symbol ist ein eindeutiger und unveränderlicher Datentyp, der als Bezeichner für Objekteigenschaften verwendet werden kann.

Wenn ein Symbol als Eigenschaftsschlüssel verwendet wird, ist es nicht aufzuzählen.

Daher werden sie nicht mithilfe von for var in Object.keys .

So können wir Symbole verwenden, um private Daten zu speichern.

const topSecret = Symbol('topSecret'); // our private key; will only be accessible on the scope of the module file
export class SecretAgent{
    constructor(secret){
        this[topSecret] = secret; // we have access to the symbol key (closure)
        this.coverStory = 'just a simple gardner';
        this.doMission = () => {
            figureWhatToDo(topSecret[topSecret]); // we have access to topSecret
        };
    }
}

Da symbols eindeutig sind, müssen wir auf das Originalsymbol verweisen, um auf die private Eigenschaft zugreifen zu können.

import {SecretAgent} from 'SecretAgent.js'
const agent = new SecretAgent('steal all the ice cream');
// ok lets try to get the secret out of him!
Object.keys(agent); // ['coverStory'] only cover story is public, our secret is kept.
agent[Symbol('topSecret')]; // undefined, as we said, symbols are always unique, so only the original symbol will help us to get the data.

Aber es ist nicht zu 100% privat; lasst uns diesen Agenten brechen! Wir können die Object.getOwnPropertySymbols Methode verwenden, um die Objektsymbole Object.getOwnPropertySymbols .

const secretKeys = Object.getOwnPropertySymbols(agent);
agent[secretKeys[0]] // 'steal all the ice cream' , we got the secret.

WeakMaps verwenden

WeakMap ist ein neuer Objekttyp, der für es6 hinzugefügt wurde.

Wie in MDN definiert

Das WeakMap-Objekt ist eine Sammlung von Schlüssel / Wert-Paaren, in denen die Schlüssel schwach referenziert werden. Die Schlüssel müssen Objekte sein und die Werte können beliebige Werte sein.

Eine weitere wichtige Funktion von WeakMap ist, wie in MDN definiert.

Der Schlüssel in einer WeakMap wird schwach gehalten. Dies bedeutet, dass der gesamte Eintrag von der WeakMap vom Garbage Collection entfernt wird, wenn keine anderen starken Verweise auf den Schlüssel vorhanden sind.

Die Idee ist, die WeakMap als statische Map für die gesamte Klasse zu verwenden, um jede Instanz als Schlüssel und die privaten Daten als Wert für diesen Instanzschlüssel zu speichern.

Daher haben wir nur innerhalb der Klasse Zugriff auf die WeakMap Sammlung.

WeakMap unseren Agenten mit WeakMap :

const topSecret = new WeakMap(); // will hold all private data of all instances.
export class SecretAgent{
    constructor(secret){
        topSecret.set(this,secret); // we use this, as the key, to set it on our instance private data
        this.coverStory = 'just a simple gardner';
        this.doMission = () => {
            figureWhatToDo(topSecret.get(this)); // we have access to topSecret
        };
    }
}

Da der const topSecret in unserem topSecret definiert ist und wir ihn nicht an unsere Instanzeigenschaften topSecret ist dieser Ansatz völlig privat und wir können den Agenten topSecret nicht erreichen.

Definieren Sie alle Methoden innerhalb des Konstruktors

Die Idee hier ist einfach, alle unsere Methoden und Member im Konstruktor zu definieren und die Schließung zu verwenden, um auf private Member zuzugreifen, ohne sie this zuzuordnen.

   export class SecretAgent{
        constructor(secret){
            const topSecret = secret;
            this.coverStory = 'just a simple gardner';
            this.doMission = () => {
                figureWhatToDo(topSecret); // we have access to topSecret
            };
        }
    }

Auch in diesem Beispiel sind die Daten zu 100% privat und können außerhalb der Klasse nicht erreicht werden, sodass unser Agent sicher ist.

Namenskonventionen verwenden

Wir werden entscheiden, dass jede Eigenschaft, die privat ist, mit _ vorangestellt wird.

Beachten Sie, dass die Daten für diesen Ansatz nicht wirklich privat sind.

export class SecretAgent{
    constructor(secret){
        this._topSecret = secret; // it private by convention
        this.coverStory = 'just a simple gardner';
        this.doMission = () => {
            figureWhatToDo(this_topSecret); 
        };
    }
}

Klassenname bindend

Der Name von ClassDeclaration ist in verschiedenen Bereichen auf unterschiedliche Weise gebunden.

  1. Der Gültigkeitsbereich, in dem die Klasse definiert ist - let Bindung
  2. Der Gültigkeitsbereich der Klasse selbst - innerhalb von { und } in class {} - const Bindung
class Foo {
  // Foo inside this block is a const binding
}
// Foo here is a let binding

Zum Beispiel,

class A {
  foo() {
    A = null; // will throw at runtime as A inside the class is a `const` binding
  }
}
A = null; // will NOT throw as A here is a `let` binding

Dies ist nicht dasselbe für eine Funktion -

function A() {
  A = null; // works
}
A.prototype.foo = function foo() {
  A = null; // works
}
A = null; // works


Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow