Zoeken…
Syntaxis
- klasse Foo {}
- class Foo verlengt Bar {}
- class Foo {constructor () {}}
- class Foo {myMethod () {}}
- class Foo {haal myProperty () {}}
- class Foo {set myProperty (newValue) {}}
- class Foo {static myStaticMethod () {}}
- class Foo {static get myStaticProperty () {}}
- const Foo = class Foo {};
- const Foo = class {};
Opmerkingen
class
ondersteuning is alleen aan JavaScript toegevoegd als onderdeel van de es6- standaard van 2015.
Javascript-klassen zijn syntactische suiker boven JavaScript's reeds bestaande prototype-gebaseerde overerving. Deze nieuwe syntaxis introduceert geen nieuw objectgeoriënteerd overervingsmodel in JavaScript, maar een eenvoudigere manier om met objecten en overerving om te gaan. Een class
declaratie is in wezen een afkorting voor handmatig definiëren constructeur function
en eigenschappen toevoegen aan het prototype van de constructeur. Een belangrijk verschil is dat functies direct kunnen worden aangeroepen (zonder het new
trefwoord), terwijl een direct geroepen klasse een uitzondering genereert.
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"; }
Als u een eerdere versie van JavaScript gebruikt, hebt u een transpiler zoals babel of google- closing -compiler nodig om de code te compileren naar een versie die het doelplatform kan begrijpen.
Klasse Constructor
Het fundamentele deel van de meeste klassen is de constructor, die de initiële status van elke instantie instelt en alle parameters verwerkt die zijn doorgegeven bij het aanroepen van new
.
Het is gedefinieerd in een class
blok alsof je een methode met de naam definiëren constructor
, al is het in feite behandeld als een speciaal geval.
class MyClass {
constructor(option) {
console.log(`Creating instance using ${option} option.`);
this.option = option;
}
}
Voorbeeld gebruik:
const foo = new MyClass('speedy'); // logs: "Creating instance using speedy option"
Een klein ding om op te merken is dat een klassenbouwer niet statisch kan worden gemaakt via het static
trefwoord, zoals hieronder beschreven voor andere methoden.
Statische methoden
Statische methoden en eigenschappen worden gedefinieerd op de klasse / constructor zelf , niet op instantieobjecten. Deze worden gespecificeerd in een klassedefinitie met behulp van het static
trefwoord.
class MyClass {
static myStaticMethod() {
return 'Hello';
}
static get myStaticProperty() {
return 'Goodbye';
}
}
console.log(MyClass.myStaticMethod()); // logs: "Hello"
console.log(MyClass.myStaticProperty); // logs: "Goodbye"
We kunnen zien dat statische eigenschappen niet zijn gedefinieerd op objectinstanties:
const myClassInstance = new MyClass();
console.log(myClassInstance.myStaticProperty); // logs: undefined
Ze zijn echter gedefinieerd op subklassen:
class MySubClass extends MyClass {};
console.log(MySubClass.myStaticMethod()); // logs: "Hello"
console.log(MySubClass.myStaticProperty); // logs: "Goodbye"
Getters en Setters
Met getters en setters kunt u aangepast gedrag definiëren voor het lezen en schrijven van een bepaalde eigenschap in uw klas. Voor de gebruiker zien ze er hetzelfde uit als elke typische eigenschap. Intern wordt echter een door u opgegeven aangepaste functie gebruikt om de waarde te bepalen wanneer de eigenschap wordt gebruikt (de getter) en om eventuele noodzakelijke wijzigingen voor te voeren wanneer de eigenschap wordt toegewezen (de setter).
In een class
definitie wordt een getter geschreven als een no-argument methode voorafgegaan door de get
trefwoord. Een setter is vergelijkbaar, behalve dat deze één argument accepteert (de nieuwe waarde die wordt toegewezen) en in plaats daarvan het set
sleutelwoord wordt gebruikt.
Hier is een voorbeeldklasse die een getter en een setter biedt voor de eigenschap .name
. Elke keer dat het wordt toegewezen, nemen we de nieuwe naam op in een interne .names_
array. Elke keer dat het wordt geopend, retourneren we de nieuwste naam.
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"]
Als u alleen een setter definieert, zal een poging om toegang te krijgen tot de eigenschap altijd undefined
terugkeren.
const classInstance = new class {
set prop(value) {
console.log('setting', value);
}
};
classInstance.prop = 10; // logs: "setting", 10
console.log(classInstance.prop); // logs: undefined
Als u alleen een getter definieert, heeft een poging om de eigenschap toe te wijzen geen effect.
const classInstance = new class {
get prop() {
return 5;
}
};
classInstance.prop = 10;
console.log(classInstance.prop); // logs: 5
Klasse-overerving
Overerving werkt net als in andere objectgeoriënteerde talen: methoden die in de superklasse zijn gedefinieerd, zijn toegankelijk in de uitbreidende subklasse.
Als de subklasse zijn eigen constructor aangeeft, moet deze de constructor van de ouders aanroepen via super()
voordat deze hier toegang toe 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"
Privé leden
JavaScript ondersteunt technisch geen privéleden als taalfunctie. Privacy - beschreven door Douglas Crockford - wordt in plaats daarvan geëmuleerd via sluitingen (behouden functiebereik) die elk bij elke instantiation-aanroep van een constructorfunctie worden gegenereerd.
Het voorbeeld van de Queue
laat zien hoe, met constructorfuncties, de lokale status kan worden behouden en ook toegankelijk kan worden gemaakt via bevoorrechte methoden.
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"]
Bij elke instantiatie van een Queue
genereert de constructor een afsluiting.
Dus zowel van een Queue
eigen methoden soort van enqueue
en dequeue
(zie Object.keys(q)
) nog steeds toegang hebben tot list
die nog steeds leeft in zijn omsluitende ruimte die op bouwtijd, is bewaard gebleven.
Gebruikmakend van dit patroon - het emuleren van privé-leden via bevoorrechte openbare methoden - moet men er rekening mee houden dat bij elke instantie extra geheugen zal worden verbruikt voor elke eigen eigenschapsmethode (het is code die niet kan worden gedeeld / hergebruikt). Hetzelfde geldt voor de hoeveelheid / grootte van de staat die binnen een dergelijke sluiting wordt opgeslagen.
Namen van dynamische methoden
Er is ook de mogelijkheid om uitdrukkingen te evalueren bij het benoemen van methoden vergelijkbaar met hoe u toegang kunt krijgen tot de eigenschappen van een object met []
. Dit kan handig zijn voor het hebben van dynamische eigenschapsnamen, maar wordt vaak gebruikt in combinatie met symbolen.
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 kunnen in klassen worden gedefinieerd om een functie uit te voeren en optioneel een resultaat te retourneren.
Ze kunnen argumenten van de beller ontvangen.
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" }
Privégegevens beheren met klassen
Een van de meest voorkomende obstakels bij het gebruik van klassen is het vinden van de juiste aanpak voor het omgaan met privé-staten. Er zijn 4 algemene oplossingen voor het omgaan met privéstaten:
Symbolen gebruiken
Symbolen zijn nieuw primitief type geïntroduceerd in ES2015, zoals gedefinieerd bij MDN
Een symbool is een uniek en onveranderlijk gegevenstype dat kan worden gebruikt als een identificatie voor objecteigenschappen.
Wanneer het symbool als een eigenschappencode wordt gebruikt, is het niet opsombaar.
Als zodanig worden ze niet onthuld met behulp van for var in
of Object.keys
.
Zo kunnen we symbolen gebruiken om privégegevens op te slaan.
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
};
}
}
Omdat symbols
uniek zijn, moeten we verwijzen naar het oorspronkelijke symbool om toegang te krijgen tot het privé-eigendom.
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.
Maar het is niet 100% privé; laten we die agent afbreken! We kunnen de methode Object.getOwnPropertySymbols
gebruiken om de symbolen van het object op te halen.
const secretKeys = Object.getOwnPropertySymbols(agent);
agent[secretKeys[0]] // 'steal all the ice cream' , we got the secret.
WeakMaps gebruiken
WeakMap
is een nieuw type object dat is toegevoegd voor es6.
Zoals gedefinieerd op MDN
Het WeakMap-object is een verzameling sleutel / waarde-paren waarin naar de sleutels zwak wordt verwezen. De sleutels moeten objecten zijn en de waarden kunnen willekeurige waarden zijn.
Een ander belangrijk kenmerk van WeakMap
is, zoals gedefinieerd op MDN .
De sleutel in een WeakMap wordt zwak vastgehouden. Dit betekent dat, als er geen andere sterke verwijzingen naar de sleutel zijn, het hele item door de vuilnisman uit de WeakMap wordt verwijderd.
Het idee is om de WeakMap te gebruiken, als een statische kaart voor de hele klasse, om elke instantie als sleutel te houden en de privégegevens te behouden als een waarde voor die instantie-sleutel.
Dus alleen binnen de klas hebben we toegang tot de WeakMap
collectie.
Laten we onze agent eens proberen, met 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
};
}
}
Omdat de const topSecret
is gedefinieerd in onze module-afsluiting en omdat we deze niet hebben gekoppeld aan onze instantie-eigenschappen, is deze aanpak volledig privé en kunnen we de agent topSecret
niet bereiken.
Definieer alle methoden in de constructor
Het idee hier is gewoon om al onze methoden en leden in de constructor te definiëren en de sluiting te gebruiken om toegang te krijgen tot privé-leden zonder ze this
toe te wijzen.
export class SecretAgent{
constructor(secret){
const topSecret = secret;
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(topSecret); // we have access to topSecret
};
}
}
Ook in dit voorbeeld zijn de gegevens 100% privé en kunnen ze niet buiten de klas worden bereikt, dus onze agent is veilig.
Naamgevingsconventies gebruiken
We zullen beslissen dat elke eigenschap die privé is, wordt voorafgegaan door _
.
Merk op dat voor deze aanpak de gegevens niet echt privé zijn.
export class SecretAgent{
constructor(secret){
this._topSecret = secret; // it private by convention
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(this_topSecret);
};
}
}
Klasse Naam bindend
De naam van ClassDeclaration is op verschillende manieren gebonden in verschillende bereiken -
- Het bereik waarin de klasse is gedefinieerd -
let
bindend - Het bereik van de klasse zelf - binnen
{
en}
inclass {}
-const
bindend
class Foo {
// Foo inside this block is a const binding
}
// Foo here is a let binding
Bijvoorbeeld,
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
Dit is niet hetzelfde voor een functie -
function A() {
A = null; // works
}
A.prototype.foo = function foo() {
A = null; // works
}
A = null; // works