Sådan forstås nøgleordet dette og kontekst i JavaScript

Som nævnt i en af mine tidligere artikler kan mastering af JavaScript fuldt ud være en lang rejse. Du er muligvis stødt på this
på din rejse som JavaScript-udvikler. Da jeg startede, så jeg det først, når jeg brugte eventListeners
og med jQuery. Senere måtte jeg bruge det ofte med React, og jeg er sikker på, at du også gjorde det. Det betyder ikke, at jeg virkelig forstod, hvad det er, og hvordan man fuldt ud tager kontrol over det.
Det er dog meget nyttigt at mestre konceptet bag det, og når det kontaktes med et klart sind, er det heller ikke særlig svært.
Gravet ind i dette
At forklarethis
kan føre til en masse forvirring, simpelthen ved navngivning af nøgleordet.
this
er tæt forbundet med hvilken kontekst du er i dit program. Lad os starte helt øverst. Hvis du bare skriver this
konsollen i vores browser, får du window
-objektet, den yderste kontekst for din JavaScript. I Node.js, hvis vi gør det:
console.log(this)
vi ender med {}
et tomt objekt. Dette er lidt underligt, men det ser ud til, at Node.js opfører sig sådan. Hvis du gør
(function() { console.log(this); })();
dog vil du modtage global
objektet, den yderste sammenhæng. I den forbindelse setTimeout
, setInterval
er gemt. Du er velkommen til at lege lidt med det for at se, hvad du kan gøre med det. Herfra er der næsten ingen forskel mellem Node.js og browseren. Jeg bruger window
. Bare husk at i Node.js vil det være global
objektet, men det gør ikke rigtig en forskel.

Husk: Kontekst giver kun mening inde i funktioner
Forestil dig, at du skriver et program uden at indlejre noget i funktioner. Du ville blot skrive en linje efter den anden uden at gå ned på specifikke strukturer. Det betyder, at du ikke behøver at holde styr på, hvor du er. Du er altid på samme niveau.
Når du begynder at have funktioner, har du muligvis forskellige niveauer i dit program og this
repræsenterer, hvor du er, hvilket objekt der kaldes funktionen.
Holde styr på objektet, der ringer op
Lad os se på følgende eksempel og se, hvordan this
ændringer afhænger af konteksten:
const coffee = { strong: true, info: function() { console.log(`The coffee is ${this.strong ? '' : 'not '}strong`) }, } coffee.info() // The coffee is strong
Da vi kalder en funktion, der er erklæret inde i coffee
objektet, ændres vores kontekst til nøjagtigt det objekt. Vi kan nu få adgang til alle objektets egenskaber igennem this
. I vores eksempel ovenfor kunne vi også bare henvise til det direkte ved at gøre coffee.strong
. Det bliver mere interessant, når vi ikke ved, hvilken kontekst, hvilket objekt, vi er i, eller når tingene simpelthen bliver lidt mere komplekse. Se på følgende eksempel:
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()
Klasser og forekomster
Klasser kan bruges til at abstrahere din kode og dele adfærd. Det info
er ikke godt at gentage funktionserklæringen i det sidste eksempel. Da klasser og deres forekomster faktisk er objekter, opfører de sig på samme måde. En ting at bemærke er dog, at erklæring this
i konstruktøren faktisk er en forudsigelse for fremtiden, når der vil være en forekomst.
Lad os se:
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: problemfrit indlejrede funktionsopkald
Nogle gange ender vi i en sammenhæng, som vi ikke rigtig havde forventet. Dette kan ske, når vi ubevidst kalder funktionen inde i en anden objektkontekst. Et meget almindeligt eksempel er, når du bruger setTimeout
eller setInterval
:
// BAD EXAMPLE const coffee = { strong: true, amount: 120, drink: function() { setTimeout(function() { if (this.amount) this.amount -= 10 }, 10) }, } coffee.drink()
Hvad tror du coffee.amount
er?
...
..
.
Det er stadig 120
. For det første var vi inde i coffee
objektet, da drink
metoden er erklæret inde i den. Det gjorde vi bare setTimeout
og intet andet. Det er netop det.
Som jeg forklarede tidligere, er setTimeout
metoden faktisk erklæret i window
objektet. Når vi kalder det, skifter vi faktisk kontekst til det window
igen. Det betyder, at vores instruktioner faktisk forsøgte at ændre window.amount
, men det endte med at gøre noget på grund af if
erklæringen. For at tage os af det er vi nødt til bind
vores funktioner (se nedenfor).
Reagere
Ved hjælp af React vil dette forhåbentlig snart høre fortiden til takket være Hooks. I øjeblikket er vi stadig nødt til bind
alt (mere om det senere) på en eller anden måde. Da jeg startede, havde jeg ingen idé om, hvorfor jeg gjorde det, men på dette tidspunkt skulle du allerede vide, hvorfor det er nødvendigt.
Lad os se på to enkle komponenter i React-klassen:
// 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, }) } }
Når vi nu klikker på knappen gengivet af Child
, modtager vi en fejl. Hvorfor? Fordi React ændrede vores kontekst, når man kalder _getCoffee
metoden.
Jeg antager, at React kalder gengivelsesmetoden for vores komponenter i en anden sammenhæng gennem hjælpeklasser eller lignende (selvom jeg bliver nødt til at grave dybere for at finde ud af det med sikkerhed). Derfor this.state
er udefineret, og vi prøver at få adgang this.state.coffeeCount
. Du skal modtage noget lignende Cannot read property coffeeCount of undefined
.
For at løse problemet skal du bind
(vi kommer derhen) metoderne i vores klasser, så snart vi sender dem ud af den komponent, hvor de er defineret.

Lad os se på et mere generisk eksempel:
// 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])
Vi passerer increaseCount
fra en klasse til en anden. Når vi kalder increaseCount
metoden ind Viking
, har vi allerede ændret kontekst og this
peger faktisk på Viking
, hvilket betyder at vores increaseCount
metode ikke fungerer som forventet.
Løsning - bind
Den enkleste løsning for os er bind
de metoder, der vil blive sendt ud af vores oprindelige objekt eller klasse. Der er forskellige måder, hvorpå du kan binde funktioner, men den mest almindelige (også i React) er at binde den i konstruktøren. Så vi bliver nødt til at tilføje denne linje i Battle
konstruktøren før linje 18:
this.increaseCount = this.increaseCount.bind(this)
Du kan binde enhver funktion til enhver kontekst. Dette betyder ikke, at du altid skal binde funktionen til den kontekst, den er erklæret i (dette er dog det mest almindelige tilfælde). I stedet kunne du binde det til en anden sammenhæng. Med indstillerbind
du altid konteksten for en funktionserklæring . Dette betyder, at alle opkald til denne funktion modtager den bundne kontekst som this
. Der er to andre hjælpere til at indstille konteksten.

Ansøg og ring
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
Dette er det, forhåbentlig har du nu en klar forståelse af, hvordan du bruger this
i JavaScript. Du er velkommen til at efterlade forslag til den næste artikel i kommentarerne.
Om forfatteren: Lukas Gisder-Dubé var medstifter af og ledede en opstart som CTO i 1 1/2 år og byggede teknologiteamet og arkitekturen. Efter at have forladt opstarten lærte han kodning som Lead Instructor hos Ironhack og bygger nu et Startup Agency & Consultancy i Berlin. Tjek dube.io for at lære mere.
