Sök…
Syntax
- klass Foo {}
- klass Foo utökar Bar {}
- klass Foo {konstruktör () {}}
- klass Foo {myMethod () {}}
- klass Foo {get myProperty () {}}
- klass Foo {set myProperty (newValue) {}}
- klass Foo {static myStaticMethod () {}}
- klass Foo {static get myStaticProperty () {}}
- const Foo = klass Foo {};
- const Foo = klass {};
Anmärkningar
class
stöd endast läggas till JavaScript som en del av 2015 ES6 standard.
Javascript-klasser är syntaktiskt socker över JavaScript: s redan existerande prototypbaserade arv. Den nya syntaxen introducerar inte en ny objektorienterad arvsmodell till JavaScript, bara ett enklare sätt att hantera objekt och arv. En class
deklaration är i huvudsak en förkortning för manuellt definiera en konstruktör function
och addera egenskaperna till prototypen för konstruktören. En viktig skillnad är att funktioner kan kallas direkt (utan det new
nyckelordet), medan en klass som kallas direkt kommer att kasta ett undantag.
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"; }
Om du använder en tidigare version av JavaScript behöver du en transpilerare som babel eller google-closure-compiler för att sammanställa koden till en version som målplattformen kan förstå.
Klasskonstruktör
Den grundläggande delen av de flesta klasser är dess konstruktör, som ställer in varje instans ursprungliga tillstånd och hanterar alla parametrar som har vidtagits när man ringde new
.
Det är definierad i en class
blocket som om du definierar en metod som heter constructor
, men det är faktiskt hanteras som ett specialfall.
class MyClass {
constructor(option) {
console.log(`Creating instance using ${option} option.`);
this.option = option;
}
}
Exempel på användning:
const foo = new MyClass('speedy'); // logs: "Creating instance using speedy option"
En liten sak att notera är att en klasskonstruktör inte kan göras statisk via det static
nyckelordet, som beskrivs nedan för andra metoder.
Statiska metoder
Statiska metoder och egenskaper definieras i klassen / konstruktören själv , inte på instansobjekt. Dessa anges i en klassdefinition med hjälp av det static
nyckelordet.
class MyClass {
static myStaticMethod() {
return 'Hello';
}
static get myStaticProperty() {
return 'Goodbye';
}
}
console.log(MyClass.myStaticMethod()); // logs: "Hello"
console.log(MyClass.myStaticProperty); // logs: "Goodbye"
Vi kan se att statiska egenskaper inte definieras i objektfall:
const myClassInstance = new MyClass();
console.log(myClassInstance.myStaticProperty); // logs: undefined
Men de definieras klasser:
class MySubClass extends MyClass {};
console.log(MySubClass.myStaticMethod()); // logs: "Hello"
console.log(MySubClass.myStaticProperty); // logs: "Goodbye"
Getters and Setters
Getters and seters låter dig definiera anpassat beteende för att läsa och skriva en viss egenskap i din klass. För användaren verkar de samma som alla typiska egenskaper. Emellertid används en anpassad funktion som du tillhandahåller för att bestämma värdet när egenskapen nås (getter) och för att förforma alla nödvändiga ändringar när egenskapen tilldelas (setter).
I en class
definition, är en getter skriven som en no-argument metod föregås av get
sökord. En setter är liknande, förutom att den accepterar ett argument (det nya värdet som tilldelas) och det set
nyckelordet används istället.
Här är en exempelklass som ger en getter och setter för sin .name
egenskap. Varje gång det tilldelas registrerar vi det nya namnet i en intern .names_
matris. Varje gång det öppnas kommer vi tillbaka det senaste namnet.
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"]
Om du bara definierar en setter, försöker du komma åt egenskapen alltid undefined
.
const classInstance = new class {
set prop(value) {
console.log('setting', value);
}
};
classInstance.prop = 10; // logs: "setting", 10
console.log(classInstance.prop); // logs: undefined
Om du bara definierar en getter har försök att tilldela egenskapen ingen effekt.
const classInstance = new class {
get prop() {
return 5;
}
};
classInstance.prop = 10;
console.log(classInstance.prop); // logs: 5
Klass ärft
Arv fungerar precis som på andra objektorienterade språk: metoder definierade på superklassen är tillgängliga i den utvidgade underklassen.
Om underklassen förklarar sin egen konstruktör måste den åberopa föräldrarnas konstruktör via super()
innan den kan komma åt 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"
Privata medlemmar
JavaScript stöder inte tekniskt privata medlemmar som språkfunktion. Sekretess - beskrivet av Douglas Crockford - emuleras istället via stängningar (bevarad funktionsomfång) som kommer att genereras var och en med varje inställningsanrop i en konstruktörsfunktion.
Exemplet i Queue
visar hur lokal konstruktion kan bevaras och göras tillgängliga via konstruktorfunktioner via privilegierade metoder.
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"]
Med varje instans av en Queue
typ skapar konstruktören en stängning.
Således både en Queue
typ egna metoder enqueue
och dequeue
(se Object.keys(q)
) fortfarande har tillgång till list
som fortsätter att leva i sin omslutande omfattning som vid byggtiden, har bevarats.
Med hjälp av detta mönster - emulering av privata medlemmar via privilegierade offentliga metoder - bör man komma ihåg att med varje instans kommer ytterligare minne att konsumeras för varje egen egendomsmetod (för det är kod som inte kan delas / återanvändas). Detsamma gäller för mängden / storleken på tillstånd som kommer att lagras inom en sådan stängning.
Dynamiska metodnamn
Det finns också förmågan att utvärdera uttryck när man namnger metoder som liknar hur du kan komma åt ett objekts egenskaper med []
. Detta kan vara användbart för att ha dynamiska egendomsnamn, men används ofta i samband med symboler.
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" }
metoder
Metoder kan definieras i klasser för att utföra en funktion och eventuellt returnera ett resultat.
De kan få argument från den som ringer.
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" }
Hantera privata data med klasser
Ett av de vanligaste hinder som använder klasser är att hitta rätt sätt att hantera privata stater. Det finns fyra vanliga lösningar för att hantera privata stater:
Använda symboler
Symboler är en ny primitiv typ som introducerades i ES2015, enligt definitionen på MDN
En symbol är en unik och oföränderlig datatyp som kan användas som en identifierare för objektegenskaper.
När du använder symbol som en egendomsnyckel är det inte många.
Som sådan kommer de inte att avslöjas med for var in
eller i Object.keys
.
Således kan vi använda symboler för att lagra privat data.
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
};
}
}
Eftersom symbols
är unika måste vi ha referens till den ursprungliga symbolen för att få åtkomst till den privata egenskapen.
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.
Men det är inte 100% privat; låt oss bryta ner agenten! Vi kan använda metoden Object.getOwnPropertySymbols
för att få objektsymbolerna.
const secretKeys = Object.getOwnPropertySymbols(agent);
agent[secretKeys[0]] // 'steal all the ice cream' , we got the secret.
Använda WeakMaps
WeakMap
är en ny typ av objekt som har lagts till för es6.
Som definierat på MDN
WeakMap-objektet är en samling nyckel- / värdepar där knapparna är svagt refererade till. Nycklarna måste vara objekt och värdena kan vara godtyckliga värden.
En annan viktig egenskap hos WeakMap
är, enligt definitionen på MDN .
Nyckeln i en WeakMap hålls svagt. Vad detta betyder är att om det inte finns några andra starka referenser till nyckeln kommer hela posten att tas bort från WeakMap av avfallssamlaren.
Tanken är att använda WeakMap, som en statisk karta för hela klassen, för att hålla varje instans som nyckel och behålla den privata informationen som ett värde för den instansnyckeln.
Således är det bara inom klassen att vi har tillgång till WeakMap
kollektionen.
Låt oss försöka vår agent med 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
};
}
}
Eftersom const topSecret
definieras i vår modulstängning, och eftersom vi inte binder den till våra instansegenskaper, är denna strategi helt privat och vi kan inte nå agenten topSecret
.
Definiera alla metoder inuti konstruktören
Idén här är helt enkelt att definiera alla våra metoder och medlemmar i konstruktören och använda stängningen för att få åtkomst till privata medlemmar utan att tilldela dem till this
.
export class SecretAgent{
constructor(secret){
const topSecret = secret;
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(topSecret); // we have access to topSecret
};
}
}
Även i detta exempel är uppgifterna 100% privata och kan inte nås utanför klassen, så vår agent är säker.
Använda namnkonventioner
Vi kommer att bestämma att alla egendomar som är privata kommer att prefixeras med _
.
Observera att för denna strategi är uppgifterna inte riktigt privata.
export class SecretAgent{
constructor(secret){
this._topSecret = secret; // it private by convention
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(this_topSecret);
};
}
}
Klassnamn bindande
ClassDeclarations namn är bundet på olika sätt i olika tillämpningsområden -
- Omfattningen i vilken klassen definieras -
let
bindande - Omfattningen av klassen själv - inom
{
och}
iclass {}
-const
bindande
class Foo {
// Foo inside this block is a const binding
}
// Foo here is a let binding
Till exempel,
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
Detta är inte detsamma för en funktion -
function A() {
A = null; // works
}
A.prototype.foo = function foo() {
A = null; // works
}
A = null; // works