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 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 lub , 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 -

  1. Zakres, w którym zdefiniowano klasę - let wiązanie
  2. Zakres samej klasy - w obrębie { i } w class {} - 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


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow