Demystifikování toho, jak to funguje v Javascriptu

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.