Classes

Klasy w Javascript zostały wprowadzone w ECMAScript 2015 jako lukier składniowy (ang. syntactic sugar) dla istniejącego, opartego na prototypach modelu dziedziczenia. Składnia klas nie wprowadza nowego zorientowanego obiektowo modelu dziedziczenia. Klasy wprowadzają znacznie prostszą i bardziej czytelną składnię do tworzenia obiektów i dziedziczenia.

Definiowanie klas

Klasy są w zasadzie "szczególnymi funkcjami". Podobnie jak w funkcji można definiować wyrażenie function i deklaracje funkcji, tak składnia klasy posiada dwa komponenty: wyrażenie class (en-US) i deklaracje klasy.

Deklaracje klas

Jednym ze sposobów definiowania klas jest deklaracja klasy. Aby zadeklarować klasę, należy użyć słowa kluczowego class wraz z nazwą klasy (w tym przypadku "Prostokat").

class Prostokat {
  constructor(wysokosc, szerokosc) {
    this.wysokosc = wysokosc;
    this.szerokosc = szerokosc;
  }
}

Hoisting

Ważną różnicą pomiędzy deklaracją funkcji a deklaracją klasy jest to, że deklaracje funkcji są przenoszone na początek (Hoisting) a klas nie. Najpierw musisz zadeklarować swoją klasę, by mieć do niej dostęp, w przeciwnym razie kod, jak ten poniżej, wygeneruje błąd ReferenceError:

var p = new Prostokat(); // ReferenceError

class Prostokat {}

Wyrażenie class

Wyrażenie class jest kolejnym sposobem definiowania klasy. Wyrażenia class mogą być nazwane lub nienazwane. Nazwa przypisana nazwanemu wyrażeniu class jest lokalna dla ciała klasy. (można ją odczytać z właściwości name (en-US) klasy)

// nienazwane
var Prostokat = class {
  constructor(wysokosc, szerokosc) {
    this.wysokosc = wysokosc;
    this.szerokosc = szerokosc;
  }
};
console.log(Prostokat.name); // Prostokat

// nazwane
var Prostokat = class Prostokat2 {
  constructor(wysokosc, szerokosc) {
    this.wysokosc = wysokosc;
    this.szerokosc = szerokosc;
  }
};
console.log(Prostokat.name); // Prostokat2

Uwaga: Wyrażenia class dotykają te same kwestie związane z przenoszeniem na początek (ang. hoisting) co wspomnianych deklaracji klas.

Ciało klasy i definicje metod

Ciało klasy jest umieszczane w nawiasach klamrowych {}. To tam definiuje się metody, czy konstruktory.

Tryb ścisły

Ciało klasy jest wykonywane w trybie ścisłym (ang. strict mode). W celu poprawienia wydajności, kod wykorzystywany tutaj podlega ścisłej składni; nie pozwala to na ukrycie niektórych wyjątków, a pewne słowa kluczowe są rezerwowane dla przyszłych wersji ECMAScript.

Konstruktor

Constructor jest szczególną metodą, która służy tworzeniu i inicjalizowaniu obiektu zdefiniowanego słowem kluczowym class. Dozwolony jest tylko jeden konstruktor w danej klasie. Jeśli klasa posiada więcej niż jedno wystąpienie metody constructor, wygenerowany zostanie błąd SyntaxError.

Aby wywołać konstruktor klasy bazowej, należy użyć słowa kluczowego super.

Metody

Zobacz też definiowanie metod.

class Prostokat {
  constructor(wysokosc, szerokosc) {
    this.wysokosc = wysokosc;
    this.szerokosc = szerokosc;
  }
  // Getter
  get pole() {
    return this.liczPole();
  }
  // Method
  liczPole() {
    return this.wysokosc * this.szerokosc;
  }
}

const kwadrat = new Prostokat(10, 10);

console.log(kwadrat.pole); // 100

Metody i właściwości statyczne

Słowo kluczowe static definiuje metodę kub właściwość statyczną w klasie. Statyczne metody i właściwości są wywoływane bez inicjalizowania ich klas i nie mogą być wywołane przez instancję klasy.

class Punkt {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  static nazwa = "Punkt";
  static odleglosc(a, b) {
    const dx = a.x - b.x;
    const dy = a.y - b.y;

    return Math.sqrt(dx*dx + dy*dy);
  }
}

const p1 = new Punkt(5, 5);
const p2 = new Punkt(10, 10);
p1.nazwa; // undefined
p1.odleglosc; // undefined
p2.nazwa; // undefined
p2.odleglosc; // undefined

console.log(Punkt.nazwa); // "Punkt"
console.log(Punkt.odleglosc(p1, p2)); // 7.0710678118654755

Powiązanie this z metodami niestatycznymi i statycznymi

Kiedy metoda typu static lub prototype jest wywoływana bez this (na przykład poprzez przypisanie metody do zmiennej), wtedy this będzie undefined w środku metody. Takie zachowanie będzie takie same, nawet jeżeli dyrektywa "use strict" nie będzie obecna, ponieważ kod w obrębie metody danej klasy zawsze będzie wykonywał się jako strict mode.

class Animal {
  speak() {
    return this;
  }
  static eat() {
    return this;
  }
}

let obj = new Animal();
obj.speak(); // obiekt Animal
let speak = obj.speak;
speak(); // undefined

Animal.eat(); // klasa Animal
let eat = Animal.eat;
eat(); // undefined

Jeśli przepiszemy powyższy przykład z użyciem tradycyjnych funkcji bez dyrektywy "use strict", to this wywołane w metodzie będzie automatycznie przypisane do pierwotnej wartości this, którą domyślnie jest global object.

function Animal() { }

Animal.prototype.speak = function() {
  return this;
}

Animal.eat = function() {
  return this;
}

let obj = new Animal();
let speak = obj.speak;
speak(); // global object

let eat = Animal.eat;
eat(); // global object

Właściwości instancji

Właściwości instancji muszą być zdefiniowane wewnątrz metody klasy:

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

Statyczne właściwości i właściwości prototypu muszą być zdefiniowane poza ciałem klasy:

Rectangle.staticWidth = 20;
Rectangle.prototype.prototypeWidth = 25;

Deklaracje pól

Publiczna i prywatne deklaracje pól są funkcjonalnościami eksperymentalnymi zaproponowanymi na TC39. Wsparcie przeglądarek jest ograniczone, ale ta funkcjonalność może być używana przy użyciu systemów takich jak Babel

Deklaracje pól publicznych

Przy użyciu deklaracji pól, powyższy przykład może być przepisany na:

class Rectangle {
  height = 0;
  width;
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

Dzięki deklarowaniu pól na początku klasy, definicje klas stają się bardziej samodokumentujące, a pola są zawsze obecne.

Jak widać w powyższym przykładzie, pola mogą być zadeklarowane z lub bez domyślnej wartości.

Zobacz public class fields po więcej informacji.

Deklaracje pól prywatnych

Używając deklaracji pól prywatnych, definicja może być zapisana w taki sposób:

class Rectangle {
  #height = 0;
  #width;
  constructor(height, width) {
    this.#height = height;
    this.#width = width;
  }
}

Próba odniesienia się do prywatnego pola poza ciałem klasy wygeneruje błąd. Prywatne pola mogą być tylko odczytywane i modyfikowane wewnątrz ciała klasy. Poprzez definicję właściwości niewidocznych poza ciałem klasy, można zapewnić, że użytkownicy klasy nie będą polegali na jej wewnętrznych właściwościach.

Pola prywatne mogą być tylko zadeklarowane na początku ciała klasy

Prywatnych pól nie da się utworzyć później, poprzez przypisywanie, tak jak normalnych właściwości.

Po więcej informacji zobacz private class fields.

Podklasy z extends

Słowo kluczowe extends jest używane w deklaracjach klas lub wyrażeniach klas do tworzenia klasy jako elementu potomnego innej klasy.

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(this.name + ' makes a noise.');
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name); // wywyłanie konstruktora klasy nadrzędnej poprzez użycie super()
  }
  speak() {
    console.log(this.name + ' barks.');
  }
}

let d = new Dog('Mitzie');
d.speak(); // Mitzie barks.

Jeśli w podklasie znajduje się konstruktor, musi najpierw wywołać super() przed użyciem "this".

Można również rozszerzyć tradycyjne klasy oparte na funkcjach:

function Animal (name) {
  this.name = name;
}

Animal.prototype.speak = function () {
  console.log(this.name + ' makes a noise.');
}

class Dog extends Animal {
  speak() {
    console.log(this.name + ' barks.');
  }
}

let d = new Dog('Mitzie');
d.speak(); // Mitzie barks

Zwróć uwagę, że klasy nie mogą rozszerzać zwykłych (niezdatnych do konstrukcji) obiektów. Jeśli chcesz dziedziczyć po zwykłym obiekcie, możesz, zamiast tego użyć Object.setPrototypeOf() (en-US):

var Animal = {
  speak() {
    console.log(this.name + ' makes a noise.');
  }
};

class Dog {
  constructor(name) {
    this.name = name;
  }
}

Object.setPrototypeOf(Dog.prototype, Animal);// If you do not do this you will get a TypeError when you invoke speak

let d = new Dog('Mitzie');
d.speak(); //Mitzie makes a noise.

Species

Jeśli chcesz zwrócić obiekt Array w twojej klasie MyArray, która dziedziczy po Array, to możesz użyć wzorca "species", który pozwala na nadpisywanie domyślnych konstruktorów.

Na przykład, wywołanie metody map() zwraca domyślny konstruktor MyArray. Użycie Symbol.species (en-US) pozwala na nadpisanie tego zachowania tak, by zwracany był obiekt typu Array, a nie MyArray:

class MyArray extends Array {
  // Nadpisanie domyślnego kontruktora
  static get [Symbol.species]() { return Array; }
}

var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);

console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array);   // true

Słowo kluczowe super

Słowo kluczowe super jest wykorzystywane do udostępniania i korzystania z funkcji klasy, po której nasz obiekt dziedziczy.

class Cat {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Lion extends Cat {
  speak() {
    super.speak();
    console.log(`${this.name} roars.`);
  }
}

let l = new Lion('Fuzzy');
l.speak();
// Fuzzy makes a noise.
// Fuzzy roars.

Mix-ins

Abstrakcyjne podklasy lub mix-ins są szablonami dla klas. Klasa może mieć tylko jedną klasę nadrzędną, więc dziedziczenie z wielu klas jest niemożliwe. Cała funkcjonalność musi być dostarczona przez jedną klasę nadrzędną.

Funkcja przyjmująca klasę nadrzędną jako argument i zwracająca podklasę rozszerzającą klasę nadrzędną może być użyta do implementacji mix-in'ów:

var calculatorMixin = Base => class extends Base {
  calc() { }
};

var randomizerMixin = Base => class extends Base {
  randomize() { }
};

Klasa używająca tych mix-in'ów może być zapisana w taki sposób:

class Foo { }
class Bar extends calculatorMixin(randomizerMixin(Foo)) { }

Specyfikacje

Specyfikacja Status Komentarz
ECMAScript 2015 (6th Edition, ECMA-262)
The definition of 'Class definitions' in that specification.
Standard Initial definition.
ECMAScript (ECMA-262)
The definition of 'Class definitions' in that specification.
Living Standard

Kompatybilność

BCD tables only load in the browser

Zobacz też