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 es6- 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 babel oder einen Google-Closure-Compiler , 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.
- Der Gültigkeitsbereich, in dem die Klasse definiert ist -
let
Bindung - Der Gültigkeitsbereich der Klasse selbst - innerhalb von
{
und}
inclass {}
-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