Hogyan lehet megérteni ezt a kulcsszót és a kontextust a JavaScript-ben

Mint egyik korábbi cikkemben említettem, a JavaScript teljes elsajátítása hosszú utat jelenthet. Lehet this, hogy JavaScript fejlesztőként találkozott az utazás során. Amikor elindultam, először láttam eventListenersa jQuery használatakor. Később gyakran kellett használnom a React-kel, és biztos vagyok benne, hogy te is használtad. Ez nem azt jelenti, hogy valóban megértettem, mi ez, és hogyan tudom teljes mértékben átvenni az irányítását.

Nagyon hasznos azonban elsajátítani a mögötte álló koncepciót, és ha tiszta elmével közelítjük meg, akkor sem túl nehéz.

Ebbe ásni

A magyarázat thissok zavart okozhat, egyszerűen a kulcsszó megnevezésével.

thisszorosan kapcsolódik ahhoz, hogy milyen programkörnyezetben van. Kezdjük egészen a tetején. Böngészőnkben, ha csak beírja thisa konzolt, megkapja az window-object-et, a legkülső környezetet a JavaScript-hez. A Node.js fájlban, ha megtesszük:

console.log(this)

végül {}egy üres tárgy. Ez egy kicsit furcsa, de úgy tűnik, hogy a Node.js így viselkedik. Ha te teszed

(function() { console.log(this); })();

azonban megkapja az globalobjektumot, a legkülső kontextust. Ebben az összefüggésben a setTimeout, setIntervaltárolódnak. Játsszon nyugodtan egy kicsit vele, hogy lássa, mit tehet vele. Innentől kezdve szinte nincs különbség a Node.js és a böngésző között. Használni fogom window. Csak ne feledje, hogy a Node.js-ben ez lesz az globalobjektum, de valójában nem jelent változást.

Ne feledje: A kontextusnak csak a funkciókban van értelme

Képzelje el, hogy programot ír be anélkül, hogy bármit is beágyazna a függvényekbe. Egyszerűen írna egy sort a másik után, anélkül, hogy lefutna konkrét struktúrákat. Ez azt jelenti, hogy nem kell nyomon követnie, hogy hol van. Mindig ugyanazon a szinten vagy.

Amikor elkezdi a funkciók használatát, előfordulhat, hogy a program különböző szintjei thisvannak, és képviseli, hogy hol van, milyen objektumnak hívják a függvényt.

A hívó objektum nyomon követése

Nézzük meg a következő példát, és nézzük meg, hogyan thisváltozik a kontextustól függően:

const coffee = { strong: true, info: function() { console.log(`The coffee is ${this.strong ? '' : 'not '}strong`) }, } coffee.info() // The coffee is strong

Mivel az coffeeobjektumon belül deklarált függvényt hívunk , a kontextusunk pontosan erre az objektumra változik. Most már hozzáférhetünk az objektum összes tulajdonságához this. A fenti példánkban egyszerűen csak hivatkozhatunk rá, ha csináljuk coffee.strong. Érdekesebbé válik, amikor nem tudjuk, milyen kontextusban, milyen tárgyban vagyunk, vagy amikor a dolgok egyszerűen kissé összetettebbé válnak. Vessen egy pillantást a következő példára:

const drinks = [ { name: 'Coffee', addictive: true, info: function() { console.log(`${this.name} is ${this.addictive ? '' : 'not '} addictive.`) }, }, { name: 'Celery Juice', addictive: false, info: function() { console.log(`${this.name} is ${this.addictive ? '' : 'not '} addictive.`) }, }, ] function pickRandom(arr) { return arr[Math.floor(Math.random() * arr.length)] } pickRandom(drinks).info()

Osztályok és példányok

Az osztályok felhasználhatók a kód elvonására és a viselkedés megosztására. infoAz utolsó példában szereplő funkciódeklaráció mindig nem jó. Mivel az osztályok és példányaik valójában objektumok, ugyanúgy viselkednek. Egy dolgot azonban meg kell jegyezni, hogy thisa konstruktorban való deklarálás valójában a jövőre vonatkozó jóslat, amikor lesz egy példány.

Lássuk:

class Coffee { constructor(strong) { this.strong = !!strong } info() { console.log(`This coffee is ${this.strong ? '' : 'not '}strong`) } } const strongCoffee = new Coffee(true) const normalCoffee = new Coffee(false) strongCoffee.info() // This coffee is strong normalCoffee.info() // This coffee is not strong

Pitfall: zökkenőmentesen beágyazott függvényhívások

Néha olyan kontextusba kerülünk, amire valójában nem is számítottunk. Ez akkor fordulhat elő, amikor öntudatlanul más objektumkörnyezeten belül hívjuk meg a függvényt. Nagyon gyakori példa a setTimeoutvagy setInterval:

// BAD EXAMPLE const coffee = { strong: true, amount: 120, drink: function() { setTimeout(function() { if (this.amount) this.amount -= 10 }, 10) }, } coffee.drink()

Szerinted mi coffee.amountaz?

...

..

.

Még mindig 120. Először is az coffeeobjektumon belül voltunk , mivel a drinkmódszer benne van deklarálva. Mi csak tettünk, setTimeoutés semmi más. Pontosan ennyi.

Amint azt korábban kifejtettem, a setTimeoutmódszert valójában az windowobjektum deklarálja . Amikor felhívjuk, valójában windowújra a kontextust kapcsoljuk át a másikra. Ez azt jelenti, hogy az utasításunk valóban megpróbált változtatni window.amount, de végül semmit sem tett a if-megállapítás miatt. Ennek gondozásához meg kell felelnie binda funkcióinknak (lásd alább).

Reagál

A React használatával ez remélhetőleg hamarosan a múlté lesz, Hooksnak köszönhetően. Jelenleg még mindig bindmindent (így később) egy vagy olyan módon kell kezelnünk . Amikor elindultam, fogalmam sem volt, miért csinálom, de ezen a ponton már tudnia kell, miért van erre szükség.

Vessünk egy pillantást két egyszerű React osztályú komponensre:

// BAD EXAMPLE import React from 'react' class Child extends React.Component { render() { return  Get some Coffee!  } } class Parent extends React.Component { constructor(props) { super(props) this.state = { coffeeCount: 0, } // change to turn into good example – normally we would do: // this._getCoffee = this._getCoffee.bind(this) } render() { return (    ) } _getCoffee() { this.setState({ coffeeCount: this.state.coffeeCount + 1, }) } }

Ha most kattintson a gombra által nyújtott Child, akkor egy hibaüzenet. Miért? Mivel a React megváltoztatta a kontextust a _getCoffeemódszer meghívásakor .

Feltételezem, hogy a React egy másik kontextusban hívja a komponenseink renderelési módszerét, segítő osztályokon vagy hasonlóakon keresztül (bár mélyebbre kellene ásnom, hogy biztosan megtudjam). Ezért this.statenincs meghatározva, és megpróbálunk hozzáférni this.state.coffeeCount. Valami ilyesmit kellene kapnia Cannot read property coffeeCount of undefined.

A probléma megoldásához meg kell bind(érünk oda) az osztályaink módszerei, amint átadjuk őket az összetevőhöz, ahol meghatározták őket.

Nézzünk meg még egy általános példát:

// BAD EXAMPLE class Viking { constructor(name) { this.name = name } prepareForBattle(increaseCount) { console.log(`I am ${this.name}! Let's go fighting!`) increaseCount() } } class Battle { constructor(vikings) { this.vikings = vikings this.preparedVikingsCount = 0 this.vikings.forEach(viking => { viking.prepareForBattle(this.increaseCount) }) } increaseCount() { this.preparedVikingsCount++ console.log(`${this.preparedVikingsCount} vikings are now ready to fight!`) } } const vikingOne = new Viking('Olaf') const vikingTwo = new Viking('Odin') new Battle([vikingOne, vikingTwo])

Átmegyünk az increaseCountegyik osztályból a másikba. Amikor behívjuk a increaseCountmódszert Viking, már megváltoztattuk a kontextust, és thisvalójában rámutatunk arra Viking, hogy a increaseCountmódszerünk nem a várt módon fog működni.

Megoldás - kötés

A legegyszerűbb megoldás számunkra bindaz eredeti objektumunkból vagy osztályunkból kihagyott módszerek. Különböző módon köthetünk függvényeket, de a leggyakoribb (a React-ben is) az, hogy a konstruktorba kötjük. Tehát ezt a sort hozzá kellene adnunk a Battlekonstruktorba a 18. sor előtt:

this.increaseCount = this.increaseCount.bind(this)

Bármely függvényt bármilyen kontextushoz köthet. Ez nem azt jelenti, hogy mindig függvényt kell kötnie ahhoz a kontextushoz, amelyben deklarálva van (ez azonban a leggyakoribb eset). Ehelyett egy másik kontextushoz kötheti. A bind-val mindig beállítja a függvénydeklaráció kontextusát . Ez azt jelenti, hogy a függvény minden hívása megkapja a kötött kontextust this. Két másik segítő van a kontextus beállításában.

A `() => {}` nyílfüggvények automatikusan hozzákapcsolják a függvényt a deklarációs kontextushoz

Jelentkezz és hívj

They both do basically the same thing, just that the syntax is different. For both, you pass the context as first argument. apply takes an array for the other arguments, with call you can just separate other arguments by comma. Now what do they do? Both of these methods set the context for one specific function call. When calling the function without call , the context is set to the default context (or even a bound context). Here is an example:

class Salad { constructor(type) { this.type = type } } function showType() { console.log(`The context's type is ${this.type}`) } const fruitSalad = new Salad('fruit') const greekSalad = new Salad('greek') showType.call(fruitSalad) // The context's type is fruit showType.call(greekSalad) // The context's type is greek showType() // The context's type is undefined

Can you guess what the context of the last showType() call is?

..

.

You’re right, it is the outermost scope, window . Therefore, type is undefined, there is no window.type

This is it, hopefully you now have a clear understanding on how to use this in JavaScript. Feel free to leave suggestions for the next article in the comments.

About the Author: Lukas Gisder-Dubé co-founded and led a startup as CTO for 1 1/2 years, building the tech team and architecture. After leaving the startup, he taught coding as Lead Instructor at Ironhack and is now building a Startup Agency & Consultancy in Berlin. Check out dube.io to learn more.