Ricerca…
Sintassi
- classe Foo {}
- la classe Foo estende la barra {}
- class Foo {costruttore () {}}
- class Foo {myMethod () {}}
- class Foo {get myProperty () {}}
- class Foo {set myProperty (newValue) {}}
- class Foo {static myStaticMethod () {}}
- class Foo {static get myStaticProperty () {}}
- const Foo = class Foo {};
- const Foo = class {};
Osservazioni
class
supporto di class
stato aggiunto a JavaScript solo come parte dello standard es6 2015.
Le classi Javascript sono zucchero sintattico sull'eredità basata su prototipo già esistente di JavaScript. Questa nuova sintassi non introduce un nuovo modello di ereditarietà orientato agli oggetti su JavaScript, solo un modo più semplice per gestire gli oggetti e l'ereditarietà. Una dichiarazione di class
è essenzialmente una scorciatoia per la definizione manuale di una function
costruzione e l'aggiunta di proprietà al prototipo del costruttore. Una differenza importante è che le funzioni possono essere chiamate direttamente (senza la new
parola chiave), mentre una classe chiamata direttamente genererà un'eccezione.
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"; }
Se stai usando una versione precedente di JavaScript avrai bisogno di un transpiler come babel o google-closure-compiler per compilare il codice in una versione che la piattaforma di destinazione sarà in grado di comprendere.
Costruttore di classe
La parte fondamentale della maggior parte delle classi è il suo costruttore, che imposta lo stato iniziale di ogni istanza e gestisce tutti i parametri che sono stati passati quando si chiama new
.
È definito in un blocco di class
come se si stesse definendo un metodo chiamato constructor
, sebbene in realtà sia gestito come un caso speciale.
class MyClass {
constructor(option) {
console.log(`Creating instance using ${option} option.`);
this.option = option;
}
}
Esempio di utilizzo:
const foo = new MyClass('speedy'); // logs: "Creating instance using speedy option"
Una piccola cosa da notare è che un costruttore di classi non può essere reso statico tramite la parola chiave static
, come descritto di seguito per altri metodi.
Metodi statici
I metodi e le proprietà statici sono definiti sulla classe / costruttore stesso , non sugli oggetti di istanza. Questi sono specificati in una definizione di classe usando la parola chiave static
.
class MyClass {
static myStaticMethod() {
return 'Hello';
}
static get myStaticProperty() {
return 'Goodbye';
}
}
console.log(MyClass.myStaticMethod()); // logs: "Hello"
console.log(MyClass.myStaticProperty); // logs: "Goodbye"
Possiamo vedere che le proprietà statiche non sono definite sulle istanze dell'oggetto:
const myClassInstance = new MyClass();
console.log(myClassInstance.myStaticProperty); // logs: undefined
Tuttavia, sono definiti nelle sottoclassi:
class MySubClass extends MyClass {};
console.log(MySubClass.myStaticMethod()); // logs: "Hello"
console.log(MySubClass.myStaticProperty); // logs: "Goodbye"
Getter e setter
Getter e setter ti consentono di definire un comportamento personalizzato per leggere e scrivere una determinata proprietà sulla tua classe. Per l'utente, appaiono come qualsiasi proprietà tipica. Tuttavia, internamente una funzione personalizzata fornita dall'utente viene utilizzata per determinare il valore quando si accede alla proprietà (il getter) e per preformare eventuali modifiche necessarie quando viene assegnata la proprietà (il setter).
In una definizione di class
, un getter è scritto come un metodo senza argomenti preceduto dalla parola chiave get
. Un setter è simile, tranne che accetta un argomento (il nuovo valore viene assegnato) e viene invece utilizzata la parola chiave set
.
Ecco una classe di esempio che fornisce un getter e setter per la sua proprietà .name
. Ogni volta che viene assegnato, registreremo il nuovo nome in un array .names_
interno. Ogni volta che si accede, restituiremo l'ultimo nome.
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"]
Se si definisce solo un setter, il tentativo di accesso alla proprietà verrà sempre restituito undefined
.
const classInstance = new class {
set prop(value) {
console.log('setting', value);
}
};
classInstance.prop = 10; // logs: "setting", 10
console.log(classInstance.prop); // logs: undefined
Se si definisce solo un getter, il tentativo di assegnare la proprietà non avrà alcun effetto.
const classInstance = new class {
get prop() {
return 5;
}
};
classInstance.prop = 10;
console.log(classInstance.prop); // logs: 5
Eredità di classe
L'ereditarietà funziona esattamente come in altri linguaggi orientati agli oggetti: i metodi definiti sulla superclasse sono accessibili nella sottoclasse estesa.
Se la sottoclasse dichiara il proprio costruttore allora deve invocare il costruttore di genitori tramite super()
prima di poter accedere a 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"
Membri privati
JavaScript non supporta tecnicamente i membri privati come funzionalità linguistica. La privacy, descritta da Douglas Crockford , viene emulata tramite le chiusure (scope delle funzioni preservate) che verranno generate ciascuna con ogni chiamata di istanziazione di una funzione di costruzione.
L'esempio Queue
dimostra come, con le funzioni di costruzione, lo stato locale possa essere preservato e reso accessibile anche tramite metodi privilegiati.
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"]
Ad ogni istanziazione di un tipo di Queue
il costruttore genera una chiusura.
Quindi sia di una Queue
propri metodi di tipo enqueue
e dequeue
(vedi Object.keys(q)
) ancora hanno accesso a list
che continua a vivere nel suo ambito di inclusione che, al tempo di costruzione, è stato conservato.
Facendo uso di questo modello - emulando membri privati tramite metodi pubblici privilegiati - si dovrebbe tenere presente che, con ogni istanza, verrà consumata memoria aggiuntiva per ogni metodo di proprietà (poiché è un codice che non può essere condiviso / riutilizzato). Lo stesso vale per la quantità / dimensione dello stato che verrà memorizzato all'interno di tale chiusura.
Nomi di metodi dinamici
Esiste anche la possibilità di valutare espressioni quando si nominano metodi simili a come è possibile accedere alle proprietà di un oggetto con []
. Questo può essere utile per avere nomi di proprietà dinamici, tuttavia è spesso usato in combinazione con Simboli.
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" }
metodi
I metodi possono essere definiti nelle classi per eseguire una funzione e, facoltativamente, restituire un risultato.
Possono ricevere argomenti dal chiamante.
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" }
Gestione dei dati personali con le classi
Uno degli ostacoli più comuni che utilizzano le classi è trovare l'approccio corretto per gestire gli stati privati. Esistono 4 soluzioni comuni per la gestione degli stati privati:
Utilizzo dei simboli
I simboli sono nuovi tipi primitivi introdotti in ES2015, come definito in MDN
Un simbolo è un tipo di dati unico e immutabile che può essere utilizzato come identificativo per le proprietà dell'oggetto.
Quando si utilizza il simbolo come chiave di proprietà, non è enumerabile.
In quanto tali, non verranno rivelati usando for var in
o Object.keys
.
Quindi possiamo usare i simboli per memorizzare dati privati.
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
};
}
}
Poiché i symbols
sono unici, è necessario fare riferimento al simbolo originale per accedere alla proprietà privata.
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.
Ma non è privato al 100%; spezziamo quell'agente! Possiamo usare il metodo Object.getOwnPropertySymbols
per ottenere i simboli dell'oggetto.
const secretKeys = Object.getOwnPropertySymbols(agent);
agent[secretKeys[0]] // 'steal all the ice cream' , we got the secret.
Utilizzo di WeakMaps
WeakMap
è un nuovo tipo di oggetto che è stato aggiunto per es6.
Come definito su MDN
L'oggetto WeakMap è un insieme di coppie chiave / valore in cui le chiavi sono deferite. Le chiavi devono essere oggetti e i valori possono essere valori arbitrari.
Un'altra caratteristica importante di WeakMap
è, come definito su MDN .
La chiave in una WeakMap è trattenuta debolmente. Ciò significa che, se non ci sono altri riferimenti forti alla chiave, l'intera voce verrà rimossa dalla WeakMap dal garbage collector.
L'idea è di usare WeakMap, come una mappa statica per l'intera classe, per mantenere ogni istanza come chiave e mantenere i dati privati come valore per quella chiave di istanza.
Quindi solo all'interno della classe avremo accesso alla collezione WeakMap
.
Proviamo con il nostro agente, con 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
};
}
}
Poiché const topSecret
è definito all'interno della chiusura del modulo e poiché non è stato topSecret
alle proprietà dell'istanza, questo approccio è totalmente privato e non è possibile raggiungere l'agente topSecret
.
Definire tutti i metodi all'interno del costruttore
L'idea qui è semplicemente quella di definire tutti i nostri metodi e membri all'interno del costruttore e utilizzare la chiusura per accedere ai membri privati senza assegnarli a this
.
export class SecretAgent{
constructor(secret){
const topSecret = secret;
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(topSecret); // we have access to topSecret
};
}
}
Anche in questo esempio i dati sono privati al 100% e non possono essere raggiunti al di fuori della classe, quindi il nostro agente è sicuro.
Utilizzo delle convenzioni di denominazione
Decideremo che qualsiasi proprietà privata sarà preceduta da _
.
Si noti che per questo approccio i dati non sono realmente privati.
export class SecretAgent{
constructor(secret){
this._topSecret = secret; // it private by convention
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(this_topSecret);
};
}
}
Nome della classe vincolante
ClassDeclaration's Name è associato in modi diversi in diversi ambiti:
- L'ambito in cui è definita la classe:
let
binding - L'ambito della classe stessa - all'interno di
{
e}
inclass {}
-const
binding
class Foo {
// Foo inside this block is a const binding
}
// Foo here is a let binding
Per esempio,
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
Questo non è lo stesso per una funzione -
function A() {
A = null; // works
}
A.prototype.foo = function foo() {
A = null; // works
}
A = null; // works