Szukaj…
Składnia
- klasa Foo {}
- klasa Foo rozszerza Bar {}
- klasa Foo {konstruktor () {}}
- klasa Foo {myMethod () {}}
- klasa Foo {get myProperty () {}}
- klasa Foo {set myProperty (newValue) {}}
- klasa Foo {static myStaticMethod () {}}
- klasa Foo {statyczny get myStaticProperty () {}}
- const Foo = klasa Foo {};
- const Foo = klasa {};
Uwagi
obsługa class
została dodana do JavaScript tylko w ramach standardu es6 2015.
Klasy JavaScript to cukier składniowy w stosunku do już istniejącego dziedziczenia opartego na prototypach JavaScript. Ta nowa składnia nie wprowadza do JavaScript nowego zorientowanego obiektowo modelu dziedziczenia, a jedynie prostszy sposób radzenia sobie z obiektami i dziedziczeniem. Deklaracja class
jest zasadniczo skrótem do ręcznego definiowania function
konstruktora i dodawania właściwości do prototypu konstruktora. Ważną różnicą jest to, że funkcje można wywoływać bezpośrednio (bez new
słowa kluczowego), podczas gdy klasa wywoływana bezpośrednio wyrzuci wyjątek.
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"; }
Jeśli używasz wcześniejszej wersji JavaScript, będziesz potrzebować transpilatora, takiego jak babel lub kompilator google-closure , aby skompilować kod w wersję zrozumiałą dla platformy docelowej.
Konstruktor klasy
Podstawową częścią większości klas jest konstruktor, który ustawia stan początkowy każdej instancji i obsługuje wszelkie parametry przekazane podczas wywoływania new
.
Jest zdefiniowany w bloku class
tak, jakbyś definiował metodę o nazwie constructor
, chociaż tak naprawdę jest traktowany jako szczególny przypadek.
class MyClass {
constructor(option) {
console.log(`Creating instance using ${option} option.`);
this.option = option;
}
}
Przykładowe użycie:
const foo = new MyClass('speedy'); // logs: "Creating instance using speedy option"
Należy zauważyć, że konstruktora klasy nie można ustawić statycznie za pomocą słowa kluczowego static
, jak opisano poniżej dla innych metod.
Metody statyczne
Metody statyczne i właściwości są definiowane na samej klasie / konstruktorze , a nie na obiektach instancji. Są one określone w definicji klasy za pomocą słowa kluczowego static
.
class MyClass {
static myStaticMethod() {
return 'Hello';
}
static get myStaticProperty() {
return 'Goodbye';
}
}
console.log(MyClass.myStaticMethod()); // logs: "Hello"
console.log(MyClass.myStaticProperty); // logs: "Goodbye"
Widzimy, że właściwości statyczne nie są zdefiniowane w instancjach obiektów:
const myClassInstance = new MyClass();
console.log(myClassInstance.myStaticProperty); // logs: undefined
Są one jednak zdefiniowane w podklasach:
class MySubClass extends MyClass {};
console.log(MySubClass.myStaticMethod()); // logs: "Hello"
console.log(MySubClass.myStaticProperty); // logs: "Goodbye"
Getters and Setters
Pobieracze i ustawiacze pozwalają zdefiniować niestandardowe zachowanie do odczytywania i zapisywania danej właściwości w klasie. Dla użytkownika wyglądają tak samo, jak każdej typowej właściwości. Jednak wewnętrznie udostępniana funkcja niestandardowa służy do określania wartości podczas uzyskiwania dostępu do właściwości (getter) oraz do wprowadzania niezbędnych zmian, gdy właściwość jest przypisywana (setter).
W definicji class
getter jest zapisywany jak metoda bez argumentu poprzedzona słowem kluczowym get
. Seter jest podobny, z tym wyjątkiem, że akceptuje jeden argument (przypisywana jest nowa wartość), a zamiast tego używane jest słowo kluczowe set
.
Oto przykładowa klasa, która udostępnia getter i setter dla swojej właściwości .name
. Za każdym razem, gdy zostanie przypisany, będziemy zapisywać nową nazwę w wewnętrznej tablicy .names_
. Za każdym razem, gdy jest dostępny, zwracamy najnowszą nazwę.
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"]
Jeśli zdefiniujesz tylko seter, próba dostępu do właściwości zawsze zwróci undefined
.
const classInstance = new class {
set prop(value) {
console.log('setting', value);
}
};
classInstance.prop = 10; // logs: "setting", 10
console.log(classInstance.prop); // logs: undefined
Jeśli zdefiniujesz tylko moduł pobierający, próba przypisania właściwości nie przyniesie żadnego efektu.
const classInstance = new class {
get prop() {
return 5;
}
};
classInstance.prop = 10;
console.log(classInstance.prop); // logs: 5
Dziedziczenie klas
Dziedziczenie działa tak samo jak w innych językach obiektowych: metody zdefiniowane w nadklasie są dostępne w rozszerzającej się podklasie.
Jeśli podklasa deklaruje swój własny konstruktor to musi wywołać rodzice konstruktora poprzez super()
, zanim będzie mógł uzyskać dostęp do 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"
Członkowie prywatni
JavaScript nie obsługuje technicznie prywatnych członków jako funkcji językowej. Prywatność - opisana przez Douglasa Crockforda - jest emulowana za pomocą zamknięć (zakres zachowanych funkcji), które będą generowane przy każdym wywołaniu instancji funkcji konstruktora.
Przykład Queue
pokazuje, jak przy pomocy funkcji konstruktora stan lokalny może zostać zachowany i udostępniony za pomocą uprzywilejowanych metod.
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"]
Przy każdej instancji typu Queue
konstruktor generuje zamknięcie.
Zatem zarówno o Queue
metod własny rodzaj za enqueue
i dequeue
(patrz Object.keys(q)
) nadal mają dostęp do list
, który nadal mieszka w swoim zakresie osłaniającego, że na czas budowy, została zachowana.
Korzystając z tego wzorca - emulując prywatnych członków za pomocą uprzywilejowanych metod publicznych - należy pamiętać, że za każdym razem dodatkowa pamięć będzie zużywana na każdą własną metodę własności (ponieważ jest to kod, którego nie można udostępniać / ponownie wykorzystywać). To samo dotyczy ilości / wielkości stanu, który będzie przechowywany w takim zamknięciu.
Nazwy metod dynamicznych
Istnieje również możliwość oceny wyrażeń podczas nazywania metod podobnych do tego, w jaki sposób można uzyskać dostęp do właściwości obiektu za pomocą []
. Może to być przydatne do posiadania dynamicznych nazw właściwości, jednak często jest używane w połączeniu z symbolami.
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" }
Metody
W klasach można zdefiniować metody do wykonywania funkcji i opcjonalnie zwracania wyniku.
Mogą otrzymywać argumenty od dzwoniącego.
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" }
Zarządzanie prywatnymi danymi za pomocą klas
Jedną z najczęstszych przeszkód w korzystaniu z klas jest znalezienie odpowiedniego podejścia do postępowania z państwami prywatnymi. Istnieją 4 typowe rozwiązania do obsługi państw prywatnych:
Korzystanie z symboli
Symbole to nowy typ prymitywny wprowadzony w ES2015, zgodnie z definicją w MDN
Symbol jest unikalnym i niezmiennym typem danych, który może być używany jako identyfikator właściwości obiektu.
Gdy używasz symbolu jako klucza właściwości, nie można go wyliczyć.
Jako takie nie zostaną ujawnione za pomocą for var in
lub Object.keys
.
W ten sposób możemy używać symboli do przechowywania prywatnych danych.
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
};
}
}
Ponieważ symbols
są unikalne, musimy mieć odniesienie do oryginalnego symbolu, aby uzyskać dostęp do własności prywatnej.
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.
Ale to nie jest w 100% prywatne; załamajmy tego agenta! Możemy użyć metody Object.getOwnPropertySymbols
, aby uzyskać symbole obiektów.
const secretKeys = Object.getOwnPropertySymbols(agent);
agent[secretKeys[0]] // 'steal all the ice cream' , we got the secret.
Używanie WeakMaps
WeakMap
to nowy typ obiektu, który został dodany do es6.
Zgodnie z definicją w MDN
Obiekt WeakMap to zbiór par klucz / wartość, w których klucze są słabo przywoływane. Klucze muszą być obiektami, a wartości mogą być dowolnymi wartościami.
Inną ważną funkcją WeakMap
jest, zgodnie z definicją w MDN .
Klucz w WeakMap jest trzymany słabo. Oznacza to, że jeśli nie ma innych silnych odniesień do klucza, cały wpis zostanie usunięty z WeakMap przez moduł odśmiecający.
Chodzi o to, aby użyć WeakMap, jako mapy statycznej dla całej klasy, do przechowywania każdej instancji jako klucza i zachowania prywatnych danych jako wartości dla tego klucza instancji.
Zatem tylko wewnątrz klasy będziemy mieli dostęp do kolekcji WeakMap
.
Wypróbuj naszego agenta dzięki 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
};
}
}
Ponieważ const topSecret
jest zdefiniowany wewnątrz naszego zamknięcia modułu, a ponieważ nie topSecret
go z właściwościami naszej instancji, to podejście jest całkowicie prywatne i nie możemy dotrzeć do agenta topSecret
.
Zdefiniuj wszystkie metody wewnątrz konstruktora
Chodzi tutaj o to, aby zdefiniować wszystkie nasze metody i elementy wewnątrz konstruktora i użyć zamknięcia, aby uzyskać dostęp do członków prywatnych bez przypisywania ich do this
.
export class SecretAgent{
constructor(secret){
const topSecret = secret;
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(topSecret); // we have access to topSecret
};
}
}
Również w tym przykładzie dane są w 100% prywatne i nie można do nich dotrzeć poza klasą, więc nasz agent jest bezpieczny.
Korzystanie z konwencji nazewnictwa
Zdecydujemy, że każda własność prywatna będzie poprzedzona przedrostkiem _
.
Pamiętaj, że w przypadku tego podejścia dane nie są tak naprawdę prywatne.
export class SecretAgent{
constructor(secret){
this._topSecret = secret; // it private by convention
this.coverStory = 'just a simple gardner';
this.doMission = () => {
figureWhatToDo(this_topSecret);
};
}
}
Wiązanie nazwy klasy
Nazwa deklaracji klasy jest powiązana na różne sposoby w różnych zakresach -
- Zakres, w którym zdefiniowano klasę -
let
wiązanie - Zakres samej klasy - w obrębie
{
i}
wclass {}
-const
bind
class Foo {
// Foo inside this block is a const binding
}
// Foo here is a let binding
Na przykład,
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
To nie to samo dla funkcji -
function A() {
A = null; // works
}
A.prototype.foo = function foo() {
A = null; // works
}
A = null; // works