16. února 2025

Statické vlastnosti a metody

Metodu můžeme také přiřadit samotné třídě jako celku. Takové metody se nazývají statické.

V deklaraci třídy jsou předznamenány klíčovým slovem static, například:

class Uživatel {
  static statickáMetoda() {
    alert(this === Uživatel);
  }
}

Uživatel.statickáMetoda(); // true

To ve skutečnosti udělá totéž, jako kdybychom tuto metodu přímo přiřadili jako vlastnost:

class Uživatel { }

Uživatel.statickáMetoda = function() {
  alert(this === Uživatel);
};

Uživatel.statickáMetoda(); // true

Hodnota this ve volání Uživatel.statickáMetoda() je samotný třídní konstruktor Uživatel (podle pravidla „objekt před tečkou“).

Statické metody se obvykle používají k implementaci funkcí, které patří třídě jako celku, ale nepatří žádnému jejímu konkrétnímu objektu.

Například máme objekty třídy Článek a potřebujeme funkci, která je bude porovnávat.

Přirozené řešení by bylo přidat statickou metodu Článek.porovnej:

class Článek {
  constructor(titulek, datum) {
    this.titulek = titulek;
    this.datum = datum;
  }

  static porovnej(článekA, článekB) {
    return článekA.datum - článekB.datum;
  }
}

// použití
let články = [
  new Článek("HTML", new Date(2019, 1, 1)),
  new Článek("CSS", new Date(2019, 0, 1)),
  new Článek("JavaScript", new Date(2019, 11, 1))
];

články.sort(Článek.porovnej);

alert( články[0].titulek ); // CSS

Zde metoda Článek.porovnej stojí „nad“ články jako způsob jejich porovnávání. Není to metoda článku, ale metoda celé třídy.

Jiným příkladem může být tzv. „tovární“ metoda.

Dejme tomu, že potřebujeme více způsobů, jak vytvořit článek:

  1. Vytvořit jej ze zadaných parametrů (titulek, datum atd.).
  2. Vytvořit prázdný článek s dnešním datem.
  3. …nebo nějak jinak.

První způsob můžeme implementovat konstruktorem. A pro druhý můžeme vytvořit statickou metodu třídy.

Například Článek.vytvořDnešní() zde:

class Článek {
  constructor(titulek, datum) {
    this.titulek = titulek;
    this.datum = datum;
  }

  static vytvořDnešní() {
    // pamatujte, že this = Článek
    return new this("Dnešní přehled", new Date());
  }
}

let článek = Článek.vytvořDnešní();

alert( článek.titulek ); // Dnešní přehled

Nyní pokaždé, když budeme potřebovat vytvořit dnešní přehled, můžeme volat Článek.vytvořDnešní(). Opět to není metoda článku, ale metoda celé třídy.

Statické metody se také používají ve třídách vztahujících se k databázím pro hledání, ukládání a odstraňování záznamů z databáze, například:

// předpokládejme, že Článek je speciální třída pro práci s články
// statická metoda pro odstranění článku podle jeho id:
Článek.odstraň({id: 12345});
Statické metody nejsou dostupné pro jednotlivé objekty

Statické metody lze volat na třídách, ale ne na jednotlivých objektech.

Nebude fungovat například tento kód:

// ...
článek.vytvořDnešní(); /// Chyba: článek.vytvořDnešní není funkce

Statické vlastnosti

Nová vlastnost
Tato vlastnost byla do jazyka přidána nedávno. Příklady fungují v nejnovější verzi Chrome.

Můžeme vytvářet i statické vlastnosti. Vypadají jako regulérní třídní vlastnosti, ale jsou předznamenány static:

class Článek {
  static vydavatel = "Ilja Kantor";
}

alert( Článek.vydavatel ); // Ilja Kantor

To je totéž jako přímé přiřazení do třídy Článek:

Článek.vydavatel = "Ilja Kantor";

Dědičnost statických vlastností a metod

Statické vlastnosti a metody se dědí.

Například Zvíře.porovnej a Zvíře.planeta v následujícím kódu budou zděděny a budou dostupné jako Králík.porovnej a Králík.planeta:

class Zvíře {
  static planeta = "Země";

  constructor(jméno, rychlost) {
    this.rychlost = rychlost;
    this.jméno = jméno;
  }

  běž(rychlost = 0) {
    this.rychlost += rychlost;
    alert(`${this.jméno} běží rychlostí ${this.rychlost}.`);
  }

  static porovnej(zvířeA, zvířeB) {
    return zvířeA.rychlost - zvířeB.rychlost;
  }

}

// Zděděn ze Zvíře
class Králík extends Zvíře {
  schovejSe() {
    alert(`${this.jméno} se schovává!`);
  }
}

let králíci = [
  new Králík("Bílý králík", 10),
  new Králík("Černý králík", 5)
];

králíci.sort(Králík.porovnej);

králíci[0].běž(); // Černý králík běží rychlostí 5.

alert(Králík.planeta); // Země

Když nyní zavoláme Králík.porovnej, bude volána zděděná metoda Zvíře.porovnej.

Jak to funguje? Opět pomocí prototypů. Jak jste už možná uhádli, extends vloží třídě Králík do [[Prototype]] odkaz na třídu Zvíře.

Králík extends Zvíře tedy vytvoří dva odkazy [[Prototype]]:

  1. Funkce Králík je prototypově zděděna z funkce Zvíře.
  2. Králík.prototype je prototypově zděděn ze Zvíře.prototype.

Výsledkem je, že dědičnost funguje pro regulérní i pro statické metody.

Ověřme si to kódem:

class Zvíře {}
class Králík extends Zvíře {}

// pro statické metody
alert(Králík.__proto__ === Zvíře); // true

// pro regulérní metody
alert(Králík.prototype.__proto__ === Zvíře.prototype); // true

Shrnutí

Statické metody se používají pro funkcionalitu, která patří třídě „jako celku“ a nevztahuje se ke konkrétní instanci třídy.

Například metoda pro porovnání Článek.porovnej(článek1, článek2) nebo tovární metoda Článek.vytvořDnešní().

V deklaraci třídy jsou označeny klíčovým slovem static.

Statické vlastnosti používáme tehdy, když chceme uložit data na úrovni třídy, rovněž nespojená s žádnou instancí.

Syntaxe je:

class MojeTřída {
  static vlastnost = ...;

  static metoda() {
    ...
  }
}

Technicky je statická deklarace totéž jako přiřazení samotné třídě:

MojeTřída.vlastnost = ...
MojeTřída.metoda = ...

Statické vlastnosti a metody se dědí.

Pro class B extends A prototyp samotné třídy B ukazuje na třídu A: B.[[Prototype]] = A. Jestliže se tedy pole nenajde v B, hledání bude pokračovat v A.

Úlohy

důležitost: 3

Jak víme, všechny objekty běžně dědí z Object.prototype a získávají přístup k „obecným“ objektovým metodám jako hasOwnProperty a podobně.

Například:

class Králík {
  constructor(jméno) {
    this.jméno = jméno;
  }
}

let králík = new Králík("Bobek");

// metoda hasOwnProperty je z Object.prototype
alert( králík.hasOwnProperty('jméno') ); // true

Pokud však výslovně uvedeme „class Králík extends Object“, bude se výsledek lišit od prostého „class Králík“?

Jaký je rozdíl?

Zde je příklad takového kódu (nefunguje – proč? opravte ho):

class Králík extends Object {
  constructor(jméno) {
    this.jméno = jméno;
  }
}

let králík = new Králík("Bobek");

alert( králík.hasOwnProperty('jméno') ); // Chyba

Nejprve se podívejme, proč poslední uvedený kód nefunguje.

Důvod bude zřejmý, když se ho pokusíme spustit. Konstruktor zděděné třídy musí volat super(), jinak nebude „definováno“ „this“.

Zde je tedy oprava:

class Králík extends Object {
  constructor(jméno) {
    super(); // při dědění je nutné volat rodičovský konstruktor
    this.jméno = jméno;
  }
}

let králík = new Králík("Bobek");

alert( králík.hasOwnProperty('jméno') ); // true

To však ještě není všechno.

I po této opravě bude stále existovat důležitý rozdíl mezi „class Králík extends Object“ a class Králík.

Jak víme, syntaxe „extends“ nastavuje dva prototypy:

  1. Mezi "prototype" konstruktorů (pro metody).
  2. Mezi samotnými konstruktory (pro statické metody).

V případě class Králík extends Object to znamená:

class Králík extends Object {}

alert( Králík.prototype.__proto__ === Object.prototype ); // (1) true
alert( Králík.__proto__ === Object ); // (2) true

Králík tedy nyní poskytuje přístup ke statickým metodám třídy Object přes třídu Králík, například:

class Králík extends Object {}

// běžně voláme Object.getOwnPropertyNames
alert ( Králík.getOwnPropertyNames({a: 1, b: 2})); // a,b

Jestliže však nemáme extends Object, pak se Králík.__proto__ nenastaví na Object.

Zde je ukázka:

class Králík {}

alert( Králík.prototype.__proto__ === Object.prototype ); // (1) true
alert( Králík.__proto__ === Object ); // (2) false (!)
alert( Králík.__proto__ === Function.prototype ); // jako standardně kterákoli funkce

// chyba, ve třídě Králík taková funkce není
alert ( Králík.getOwnPropertyNames({a: 1, b: 2})); // Chyba

Králík tedy v tomto případě neposkytuje přístup ke statickým metodám třídy Object.

Mimochodem, Function.prototype obsahuje „obecné“ funkční metody, např. call, bind atd. Ty jsou definitivně dostupné v obou případech, protože pro zabudovaný konstruktor třídy Object platí Object.__proto__ === Function.prototype.

Zde je obrázek:

Abychom to tedy zkrátili, existují dva rozdíly:

class Králík class Králík extends Object
– musí v konstruktoru volat super()
Králík.__proto__ === Function.prototype Králík.__proto__ === Object
Mapa tutoriálu

Komentáře

přečtěte si před komentováním…
  • Máte-li návrhy na zlepšení, vytvořte prosím issue na GitHubu nebo pull request místo komentáře.
  • Pokud v článku něčemu nerozumíte, napište prosím, čemu přesně a na kterém místě.
  • Pro vložení několika slov kódu použijte značku <code>, pro několik řádků je obalte značkou <pre>, pro více než 10 řádků vložte odkaz na pískoviště (plnkr, jsbin, codepen…)