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å thispå din rejse som JavaScript-udvikler. Da jeg startede, så jeg det først, når jeg brugte eventListenersog 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 forklare thiskan føre til en masse forvirring, simpelthen ved navngivning af nøgleordet.

thiser tæt forbundet med hvilken kontekst du er i dit program. Lad os starte helt øverst. Hvis du bare skriver thiskonsollen 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 globalobjektet, den yderste sammenhæng. I den forbindelse setTimeout, setIntervaler 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 globalobjektet, 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 thisrepræ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 coffeeobjektet, æ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 infoer 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 thisi 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 setTimeouteller 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.amounter?

...

..

.

Det er stadig 120. For det første var vi inde i coffeeobjektet, da drinkmetoden er erklæret inde i den. Det gjorde vi bare setTimeoutog intet andet. Det er netop det.

Som jeg forklarede tidligere, er setTimeoutmetoden faktisk erklæret i windowobjektet. Når vi kalder det, skifter vi faktisk kontekst til det windowigen. Det betyder, at vores instruktioner faktisk forsøgte at ændre window.amount, men det endte med at gøre noget på grund af iferklæringen. For at tage os af det er vi nødt til bindvores 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 bindalt (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 _getCoffeemetoden.

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.stateer 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 increaseCountfra en klasse til en anden. Når vi kalder increaseCountmetoden ind Viking, har vi allerede ændret kontekst og thispeger faktisk på Viking, hvilket betyder at vores increaseCountmetode ikke fungerer som forventet.

Løsning - bind

Den enkleste løsning for os er bindde 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 Battlekonstruktø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.

Pilfunktioner `() => {}` binder automatisk funktionen til erklæringskonteksten

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 thisi 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.