19. ledna 2025

Objektové metody, „this“

Objekty se obvykle vytvářejí tak, aby představovaly entity ze skutečného světa, například uživatele, objednávky a podobně:

let uživatel = {
  jméno: "Jan",
  věk: 30
};

A ve skutečném světě může uživatel konat nějakou akci: vybrat si něco z nákupního vozíku, přihlásit se, odhlásit se atd.

V JavaScriptu jsou tyto akce reprezentovány funkcemi ve vlastnostech.

Příklady metod

Pro začátek naučme objekt uživatel říci ahoj:

let uživatel = {
  jméno: "Jan",
  věk: 30
};

uživatel.řekniAhoj = function() {
  alert("Ahoj!");
};

uživatel.řekniAhoj(); // Ahoj!

Právě jsme použili funkční výraz, kterým jsme vytvořili funkci a přiřadili ji do vlastnosti objektu uživatel.řekniAhoj.

Pak ji můžeme volat pomocí uživatel.řekniAhoj(). Uživatel nyní umí mluvit!

Funkce, která je vlastností objektu, se nazývá jeho metoda.

Zde tedy máme metodu řekniAhoj objektu uživatel.

Samozřejmě můžeme použít jako metodu předem deklarovanou funkci, například takto:

let uživatel = {
  // ...
};

// nejprve ji deklarujeme
function řekniAhoj() {
  alert("Ahoj!");
};

// pak přidáme jako metodu
uživatel.řekniAhoj = řekniAhoj;

uživatel.řekniAhoj(); // Ahoj!
Objektově orientované programování

Způsob programování, při kterém píšeme kód, který používá objekty představující entity, se nazývá objektově orientované programování, zkráceně „OOP“.

OOP je velká věc a samo o sobě je zajímavou vědou. Jak zvolit správné entity? Jak zorganizovat interakci mezi nimi? To je architektura a o tomto tématu existují skvělé knihy, např. „Design Patterns: Elements of Reusable Object-Oriented Software“ od E. Gammy, R. Helma, R. Johnsona a J. Vissidese, nebo „Object-Oriented Analysis and Design with Applications“ od G. Booche a další.

Zkratky metod

V objektovém literálu existuje i kratší syntaxe metod:

// tyto objekty dělají totéž

uživatel = {
  řekniAhoj: function() {
    alert("Ahoj");
  }
};

// zkratka metody vypadá lépe, že?
uživatel = {
  řekniAhoj() { // totéž jako „řekniAhoj: function(){...}“
    alert("Ahoj");
  }
};

Jak vidíme, můžeme vypustit slovo "function" a napsat jen řekniAhoj().

Upřímně řečeno, tyto notace nejsou zcela identické. Jsou v nich drobné rozdíly týkající se objektové dědičnosti (která bude vysvětlena později), ale na nich nám prozatím nezáleží. Téměř ve všech případech se dává přednost kratší syntaxi.

„this“ v metodách

Běžně se stává, že objektová metoda potřebuje přístup k informaci uložené v objektu, aby mohla vykonat svou práci.

Například kód uvnitř uživatel.řekniAhoj() může potřebovat jméno objektu uživatel.

K přístupu do objektu může metoda používat klíčové slovo this.

Hodnota this je objekt „před tečkou“, tedy ten, který byl použit k volání metody.

Například:

let uživatel = {
  jméno: "Jan",
  věk: 30,

  řekniAhoj() {
    // „this“ je „aktuální objekt“
    alert(this.jméno);
  }

};

uživatel.řekniAhoj(); // Jan

Zde během výkonu uživatel.řekniAhoj() hodnota this bude uživatel.

Technicky je možné přistupovat k objektu i bez this tak, že se na něj odkážeme přes vnější proměnnou:

let uživatel = {
  jméno: "Jan",
  věk: 30,

  řekniAhoj() {
    alert(uživatel.jméno); // „uživatel“ namísto „this“
  }

};

…Takový kód je však nespolehlivý. Pokud se rozhodneme zkopírovat objekt uživatel do jiné proměnné, např. správce = uživatel, a přepsat proměnnou uživatel něčím jiným, pak budeme přistupovat k nesprávnému objektu.

To je ukázáno zde:

let uživatel = {
  jméno: "Jan",
  věk: 30,

  řekniAhoj() {
    alert( uživatel.jméno ); // povede k chybě
  }

};


let správce = uživatel;
uživatel = null; // přepíšeme, aby to bylo zřejmé

správce.řekniAhoj(); // TypeError: Cannot read property 'jméno' of null

Kdybychom uvnitř alert použili this.jméno namísto uživatel.jméno, kód by fungoval.

„this“ není vázané

V JavaScriptu se klíčové slovo this chová jinak než ve většině ostatních programovacích jazyků. Může být použito v libovolné funkci, i když to není metoda objektu.

V následujícím příkladu není žádná syntaktická chyba:

function řekniAhoj() {
  alert( this.jméno );
}

Hodnota this se vyhodnocuje za běhu skriptu v závislosti na kontextu.

Například zde bude stejná funkce přiřazena dvěma různým objektům a ve voláních bude mít různá „this“:

let uživatel = { jméno: "Jan" };
let správce = { jméno: "Správce" };

function řekniAhoj() {
  alert( this.jméno );
}

// použijeme stejnou funkci ve dvou objektech
uživatel.f = řekniAhoj;
správce.f = řekniAhoj;

// tato volání mají různá this
// „this“ uvnitř funkce je objekt „před tečkou“
uživatel.f(); // Jan  (this == uživatel)
správce.f(); // Správce  (this == správce)

správce['f'](); // Správce (k metodě přistupuje tečka nebo hranaté závorky - na tom nezáleží)

Platí jednoduché pravidlo: je-li volána obj.f(), pak this během volání f je obj. Ve výše uvedeném příkladu je to tedy uživatel anebo správce.

Volání bez objektu: this == undefined

Můžeme tuto funkci volat dokonce zcela bez objektu:

function řekniAhoj() {
  alert(this);
}

řekniAhoj(); // undefined

Ve striktním režimu je this v tomto případě undefined. Pokud se pokusíme přistoupit k this.jméno, nastane chyba.

V nestriktním režimu bude hodnota this v takovém případě globální objekt (v prohlížeči window, dostaneme se k tomu později v kapitole Globální objekt). To je historické chování, které "use strict" opravuje.

Takové volání je obvykle programovací chyba. Jestliže je this uvnitř funkce, očekává se, že funkce bude volána v kontextu objektu.

Důsledky nevázaného this

Pokud přicházíte z jiného programovacího jazyka, pak jste pravděpodobně zvyklí na myšlenku „vázaného this“, kdy v metodách definovaných v nějakém objektu this vždy odkazuje na tento objekt.

V JavaScriptu je this „volné“, jeho hodnota se vypočítává až při volání a není závislá na tom, kde byla metoda deklarována, ale jen na tom, jaký objekt je „před tečkou“.

Koncept vyhodnocování this za běhu má své výhody i nevýhody. Na jednu stranu můžeme tutéž funkci použít opakovaně pro různé objekty. Na druhou stranu větší flexibilita vytváří více prostoru pro chyby.

Zde nám nepřísluší soudit, zda je toto rozhodnutí návrhářů jazyka dobré nebo špatné. Porozumíme tomu, jak s ním pracovat, jak využít jeho výhody a vyhnout se problémům.

Šipkové funkce nemají „this“

Šipkové funkce jsou zvláštní: nemají „své vlastní“ this. Pokud se v takové funkci odkážeme na this, bude převzato z vnější „normální“ funkce.

Například zde šipka() používá this z vnější metody uživatel.řekniAhoj():

let uživatel = {
  křestníJméno: "Ilja",
  řekniAhoj() {
    let šipka = () => alert(this.křestníJméno);
    šipka();
  }
};

uživatel.řekniAhoj(); // Ilja

To je speciální vlastnost šipkových funkcí. Je užitečná, když ve skutečnosti nechceme mít oddělené this, ale chceme je převzít z vnějšího kontextu. Později v kapitole Šipkové funkce podruhé prozkoumáme šipkové funkce více do hloubky.

Shrnutí

  • Funkce uložené ve vlastnostech objektu se nazývají „metody“.
  • Metody umožňují objektům „konat akce“, např. objekt.dělejNěco().
  • Metody se na tento objekt mohou odkazovat klíčovým slovem this.

Hodnota this je definována za běhu skriptu.

  • Když je funkce deklarována, může používat this, ale toto this nemá žádnou hodnotu, dokud není funkce volána.
  • Funkce může být kopírována mezi objekty.
  • Když je funkce volána „metodovou“ syntaxí objekt.metoda(), hodnota this během tohoto volání je objekt.

Prosíme všimněte si, že šipkové funkce jsou zvláštní: nemají this. Když uvnitř šipkové funkce přistoupíme k this, převezme se zvnějšku.

Úlohy

důležitost: 5

Zde funkce vytvořUživatele vrátí objekt.

Jaký je výsledek přístupu k jeho vlastnosti odkaz? Proč?

function vytvořUživatele() {
  return {
    jméno: "Jan",
    odkaz: this
  };
}

let uživatel = vytvořUživatele();

alert( uživatel.odkaz.jméno ); // Jaký je výsledek?

Odpověď: chyba.

Zkuste to:

function vytvořUživatele() {
  return {
    jméno: "Jan",
    odkaz: this
  };
}

let uživatel = vytvořUživatele();

alert( uživatel.odkaz.jméno ); // Chyba: Nelze načíst vlastnost 'jméno' objektu undefined

Je to proto, že pravidla, která nastavují this, se nedívají do definice objektu. Záleží jen na okamžiku volání.

Zde je hodnota this uvnitř vytvořUživatele() undefined, protože tato funkce je volána jako funkce, ne jako metoda „tečkovou“ syntaxí.

Hodnota this je stejná pro celou funkci, bloky kódu a objektové literály ji neovlivňují.

Takže odkaz: this vezme ve skutečnosti aktuální this této funkce.

Můžeme funkci přepsat a vrátit stejné this s hodnotou undefined:

function vytvořUživatele(){
  return this; // tentokrát tady není objektový literál
}

alert( vytvořUživatele().jméno ); // Chyba: Nelze načíst vlastnost 'jméno' objektu undefined

Jak vidíte, výsledek alert( vytvořUživatele().jméno ) je stejný jako výsledek alert( uživatel.odkaz.jméno ) z předchozího příkladu.

Toto je opačný příklad:

function vytvořUživatele() {
  return {
    jméno: "Jan",
    odkaz() {
      return this;
    }
  };
}

let uživatel = vytvořUživatele();

alert( uživatel.odkaz().jméno ); // Jan

Teď to funguje, protože uživatel.odkaz() je metoda. A hodnota this se nastaví na objekt před tečkou ..

důležitost: 5

Vytvořte objekt kalkulátor se třemi metodami:

  • načti() se zeptá na dvě hodnoty a uloží je jako vlastnosti objektu pod názvy po řadě a a b.
  • součet() vrátí součet uložených hodnot.
  • součin() vynásobí uložené hodnoty mezi sebou a vrátí výsledek.
let kalkulátor = {
  // ... váš kód ...
};

kalkulátor.načti();
alert( kalkulátor.součet() );
alert( kalkulátor.součin() );

Spustit demo

Otevřít pískoviště s testy.

let kalkulátor = {
  součet() {
    return this.a + this.b;
  },

  součin() {
    return this.a * this.b;
  },

  načti() {
    this.a = +prompt('a?', 0);
    this.b = +prompt('b?', 0);
  }
};

kalkulátor.načti();
alert( kalkulátor.součet() );
alert( kalkulátor.součin() );

Otevřít řešení s testy na pískovišti.

důležitost: 2

Máme objekt žebřík, který nám umožňuje chodit nahoru a dolů:

let žebřík = {
  stupeň: 0,
  nahoru() {
    this.stupeň++;
  },
  dolů() {
    this.stupeň--;
  },
  zobrazStupeň: function() { // zobrazí aktuální stupeň
    alert( this.stupeň );
  }
};

Když nyní potřebujeme učinit několik volání po sobě, můžeme to udělat takto:

žebřík.nahoru();
žebřík.nahoru();
žebřík.dolů();
žebřík.zobrazStupeň(); // 1
žebřík.dolů();
žebřík.zobrazStupeň(); // 0

Upravte kód funkcí nahoru, dolů a zobrazStupeň tak, aby bylo možné volání zřetězit takto:

žebřík.nahoru().nahoru().dolů().zobrazStupeň().dolů().zobrazStupeň(); // zobrazí 1, pak 0

Takový přístup se zeširoka používá v JavaScriptových knihovnách.

Otevřít pískoviště s testy.

Řešením je v každém volání vrátit tento objekt samotný.

let žebřík = {
  stupeň: 0,
  nahoru() {
    this.stupeň++;
    return this;
  },
  dolů() {
    this.stupeň--;
    return this;
  },
  zobrazStupeň() {
    alert( this.stupeň );
    return this;
  }
};

žebřík.nahoru().nahoru().dolů().zobrazStupeň().dolů().zobrazStupeň(); // zobrazí 1, pak 0

Můžeme také psát každé volání na nový řádek. U delšího zřetězení je to čitelnější:

žebřík
  .nahoru()
  .nahoru()
  .dolů()
  .zobrazStupeň() // 1
  .dolů()
  .zobrazStupeň(); // 0

Otevřít řešení s testy na pískovišti.

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…)