Je to něco, co se v Javascriptu používá neustále, ale často je to, na co se odkazuje, záhadou. V JavaScriptu this
funguje zcela odlišně od jiných programovacích jazyků – a funguje odlišně v závislosti na tom, zda používáte přísný režim nebo ne.
Pokud vám to přijde těžké, nejste sami. Podívejme se, jak přesně to funguje, a odstraníme zmatky ohledně toho, co to znamená v různých kontextech.
Co je to v Javascriptu
this
je klíčové slovo v Javascriptu, které odkazuje na vlastnost nebo sadu vlastností v určitém kontextu. Kontext, ve kterém to používáme, mění jeho vlastnosti. V globálním kontextu se to týká globálního objektu – což je v prohlížeči okno, ale v Node.JS a dalších implementacích Javascriptu je globální.
console.log(this); // The same as console.log(window);
Mimo jakékoli funkce nebo kód je tomu tak vždy. Na různých místech to však znamená různé věci.
Toto v části Funkce v Javascriptu
Ve funkci to stále odkazuje na globální objekt. Pokud na to odkazujeme ve funkci, bude ve výchozím nastavení odkazovat na okno nebo objekt globalThis:
console.log(this); // The same as console.log(window);
function myFunction() {
console.log(this); // The same as console.log(window);
}
myFunction();
V přísném režimu však toto uvnitř funkce není definováno.
"use strict"
console.log(this); // The same as console.log(window);
function myFunction() {
console.log(this); // This is undefined!
}
myFunction();
Řešení pomocí call()
To je zpočátku trochu matoucí, ale důvodem je to, že musíme přidat tento objekt do myFunction - Javascript v přísném režimu jej nenastaví jako výchozí globální objekt. K tomu musíme použít call(). V níže uvedeném příkladu jsem převedl myObject na naši tuto proměnnou:
"use strict"
console.log(this); // The same as console.log(window);
let myObject = {
firstName: "John",
lastName: "Doe",
age: 76
}
function myFunction() {
console.log(this.firstName);
}
myFunction.call(myObject); // this.firstName is defined as "John", so it will console log John
myFunction(); // this.firstName will be undefined, and this will throw an error.
call()
běží myFunction
a ke klíčovému slovu this připojí myObject. Pokud nepoužijeme call a jednoduše spustíme myFunction(), pak funkce vrátí chybu, protože this.firstName nebude definováno. Můžete také volat funkci s prázdným this, ke které pak můžete připojit data uvnitř vaší funkce.
To nám dává nový prostor pro definování proměnných na našem objektu this, spíše než abychom byli znečištěni daty z globálního objektu this:
"use strict"
console.log(this); // The same as console.log(window);
function myFunction() {
this.firstName = 'John';
console.log(this.firstName); // This will be "John"
}
myFunction.call({});
Odlišné chování v přísném režimu
Jak můžete vidět, chování je zcela odlišné v závislosti na tom, zda používáme přísný režim nebo ne – proto je důležité, abyste před změnou kódu mezi těmito dvěma režimy provedli nějaké testy.
Zavolejte a požádejte
Někdy můžete vidět call()
používá se zaměnitelně s funkcí nazvanou apply()
. Obě tyto funkce jsou velmi podobné v tom, že obě vyvolávají funkci se specifikovaným tímto kontextem. Jediný rozdíl je apply()
má pole, pokud má funkce argumenty, zatímco call()
bere každý argument jeden po druhém.
Například:
"use strict"
let otherNumbers = {
a: 10,
b: 4
}
function multiplyNumbers(x, y, z) {
return this.a * this.b * x * y * z
}
// Both will return the same result, the only difference
// being that apply() uses an array for arguments.
multiplyNumbers.call(otherNumbers, 1, 2, 3);
multiplyNumbers.apply(otherNumbers, [ 1, 2, 3 ]);
Zjednodušení tohoto procesu pomocí bind()
Další způsob, jak dosáhnout podobného chování jako call()
je použít bind()
. Podobné jako call()
, bind()
, změní hodnotu this pro funkci, pouze to dělá trvale. To znamená, že nemusíte neustále používat bind()
- použijete jej pouze jednou.
Zde je příklad, kdy náš objekt natrvalo svážeme s naší funkcí, čímž ji trvale aktualizujeme – stačí ji definovat jako novou funkci. V níže uvedeném příkladu definujeme novou funkci nazvanou boundFunction, což je naše funkce myFunction s trvale svázanou funkcí myObject.
Když tedy zavoláme konzolový log, zobrazí se „John“. To se liší od volání, které je třeba použít pokaždé, když použijeme funkci.
"use strict"
console.log(this); // The same as console.log(window);
let myObject = {
firstName: "John",
lastName: "Doe",
age: 76
}
function myFunction() {
console.log(this.firstName);
}
let boundFunction = myFunction.bind(myObject); // this will bind this to myObject permanently.
boundFunction(); // since we used bind, this will now be set to myObject, every time we call boundFunction() - so it will return John.
Funkce šipky a toto
Jednou z klíčových vlastností funkcí šipky v Javascriptu je, že nemají tento kontext. To znamená, že to zdědí po svém rodiči. Řekněme například, že jsme v přísném režimu a definujeme jak funkci šipky, tak funkci „normálního“ stylu. U funkce šipky to bude zděděno, ale u jiné funkce to zůstane nedefinované!
"use strict"
console.log(this); // The same as console.log(window);
function myFunction() {
console.log(this.name); // This will be "John"
let myArrowFunction = () => {
console.log(this.name); // This will be "John"
}
let myNormalFunction = function() {
console.log(this.name); // This will throw an error, since this is undefined!
}
myArrowFunction();
myNormalFunction();
}
myFunction.call({
name: "John"
});
Funkce konstruktoru a toto
Další zajímavá věc na tom je, že při použití ve funkci konstruktoru (což je funkce používající klíčové slovo new) to návrat funkce konstruktoru v podstatě přepíše. Pokud tedy například spustíme následující, ačkoliv nastavíme this.name na John, hodnota vrácená pro jméno je Jack:
let functionA = function() {
this.name = "John";
}
let functionB = function() {
this.name = "John";
return {
name: "Jack"
}
}
let runFunctionA = new functionA();
console.log(runFunctionA.name); // Returns "John";
let runFunctionB = new functionB();
console.log(runFunctionB.name); // Returns "Jack";
Toto v kontextu objektu
V kontextu objektu se pomocí tohoto odkazuje na objekt. Předpokládejme například, že spustíme funkci v objektu s názvem obj, který odkazuje na this.aProperty
- to se v tomto případě týká obj
:
let obj = {
aProperty: 15,
runFunction: function() {
console.log(this.aProperty); // Refers to 15
}
}
obj.runFunction(); // Will console log 15, since this refers to obj
To platí také v případě, že použijete zápis get()/set():
"use strict"
let obj = {
aProperty: 15,
runFunction: function() {
console.log(this.aProperty); // Refers to 15
},
set updateProp(division) {
this.aProperty = this.aProperty / division; // this.aProperty refers to 15
console.log(this.aProperty);
}
}
obj.updateProp = 15; // Will divide aProperty by 15, and console log the result, i.e. 1
Použití s posluchači událostí
Další zvláštností Javascriptu je to, že při použití posluchače událostí to odkazuje na prvek HTML, do kterého byla událost přidána. V níže uvedeném příkladu přidáme událost kliknutí ke značce HTML s ID „hello-world“:
document.getElementById('hello-world').addEventListener('click', function(e) {
console.log(this);
});
Pokud pak klikneme na naše #hello-world
HTML element, uvidíme to v našem protokolu konzoly:
<div id="hello-world"></div>
Použití s třídami
V této části stojí za zmínku, že třídy v Javascriptu jsou prostě funkce pod kapotou. To znamená, že mnoho funkcí, které jsme viděli u funkcí, platí pro třídy.
Ve výchozím nastavení bude mít třída toto nastavení na samotnou instanci třídy. V níže uvedeném příkladu to můžeme vidět v akci – jak runClass.name, tak runClass.whatsMyName vrátí Johna.
class myClass {
whatsMyName() {
return this.name;
}
get name() {
return "John";
}
}
const runClass = new myClass();
console.log(runClass.name); // Returns "John"
console.log(runClass.whatsMyName); // Returns "John"
Jedinou výjimkou je, že se k tomu nepřidávají statické položky. Pokud tedy definujeme funkci s klíčovým slovem static před ní, nebude na tomto:
class myClass {
getMyAge() {
return this.whatsMyAge();
}
static whatsMyAge() {
return this.age;
}
get name() {
return "John";
}
get age() {
return 143
}
}
const runClass = new myClass();
console.log(runClass.whatsMyAge()); // Throws an error, since runClass.whatsMyAge() is undefined
console.log(runClass.getMyAge()); // Throws an error, since this.whatsMyAge() is undefined
Stojí za zmínku, že třídy jsou ve výchozím nastavení vždy v přísném režimu - takže se to bude chovat stejným způsobem, jako ve výchozím nastavení pro přísné funkce ve třídách.
Závěr
V Javascriptu to může znamenat různé věci. V tomto článku jsme probrali, co to znamená v různých kontextech – funkce, třídy a objekty. Popsali jsme, jak používat bind()
, call()
a apply()
pro přidání jiného kontextu do vašich funkcí.
Také jsme se zabývali tím, jak to použít v přísném režimu oproti nepřísnému režimu. Po tomto, doufám, this
je mírně demystifikován.