Wie man das Schlüsselwort this und den Kontext in JavaScript versteht

Wie in einem meiner früheren Artikel erwähnt, kann das vollständige Beherrschen von JavaScript eine lange Reise sein. Möglicherweise sind Sie thisauf Ihrer Reise als JavaScript-Entwickler auf etwas gestoßen. Als ich anfing, sah ich es zum ersten Mal bei der Verwendung von eventListenersund mit jQuery. Später musste ich es oft mit React verwenden und ich bin mir sicher, dass Sie es auch getan haben. Das bedeutet nicht, dass ich wirklich verstanden habe, was es ist und wie ich die volle Kontrolle darüber übernehmen kann.

Es ist jedoch sehr nützlich, das dahinter stehende Konzept zu beherrschen, und wenn man es mit klarem Verstand angeht, ist es auch nicht sehr schwierig.

Darin graben

Das Erklären thiskann zu großer Verwirrung führen, einfach durch die Benennung des Schlüsselworts.

thisist eng an den Kontext gekoppelt, in dem Sie sich in Ihrem Programm befinden. Beginnen wir ganz oben. Wenn Sie in unserem Browser nur thisdie Konsole windoweingeben, erhalten Sie das -object, den äußersten Kontext für Ihr JavaScript. Wenn wir in Node.js Folgendes tun:

console.log(this)

Am Ende haben wir {}ein leeres Objekt. Das ist ein bisschen komisch, aber es scheint, als würde sich Node.js so verhalten. Wenn Sie tun

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

Sie erhalten jedoch das globalObjekt, den äußersten Kontext. In diesem Zusammenhang setTimeout, setIntervalwerden gespeichert. Fühlen Sie sich frei, ein bisschen damit herumzuspielen, um zu sehen, was Sie damit machen können. Ab hier gibt es fast keinen Unterschied zwischen Node.js und dem Browser. Ich werde verwenden window. Denken Sie daran, dass es in Node.js das globalObjekt sein wird, aber es macht keinen wirklichen Unterschied.

Denken Sie daran: Der Kontext ist nur innerhalb von Funktionen sinnvoll

Stellen Sie sich vor, Sie schreiben ein Programm, ohne etwas in Funktionen zu verschachteln. Sie würden einfach eine Zeile nach der anderen schreiben, ohne bestimmte Strukturen zu durchlaufen. Das heißt, Sie müssen nicht verfolgen, wo Sie sich befinden. Sie sind immer auf dem gleichen Niveau.

Wenn Sie anfangen, Funktionen zu haben, haben Sie möglicherweise verschiedene Ebenen Ihres Programms und thisstellen dar, wo Sie sich befinden, welches Objekt die Funktion genannt wird.

Verfolgen Sie das Aufruferobjekt

Schauen wir uns das folgende Beispiel an und sehen, wie sich die thisÄnderungen je nach Kontext ändern:

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

Da wir eine Funktion aufrufen, die im coffeeObjekt deklariert ist , ändert sich unser Kontext in genau dieses Objekt. Wir können jetzt über auf alle Eigenschaften dieses Objekts zugreifen this. In unserem obigen Beispiel könnten wir auch direkt darauf verweisen coffee.strong. Interessanter wird es, wenn wir nicht wissen, in welchem ​​Kontext, in welchem ​​Objekt wir uns befinden oder wenn die Dinge einfach etwas komplexer werden. Schauen Sie sich das folgende Beispiel an:

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()

Klassen und Instanzen

Klassen können verwendet werden, um Ihren Code zu abstrahieren und das Verhalten zu teilen. Es infoist nicht gut , die Funktionsdeklaration im letzten Beispiel immer zu wiederholen . Da Klassen und ihre Instanzen tatsächlich Objekte sind, verhalten sie sich genauso. Zu beachten ist jedoch, dass das Deklarieren thisim Konstruktor tatsächlich eine Vorhersage für die Zukunft ist, wenn es eine Instanz geben wird.

Lass uns einen Blick darauf werfen:

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

Fallstricke: nahtlos verschachtelte Funktionsaufrufe

Manchmal geraten wir in einen Kontext, den wir nicht wirklich erwartet hatten. Dies kann passieren, wenn wir die Funktion unwissentlich in einem anderen Objektkontext aufrufen. Ein sehr häufiges Beispiel ist bei der Verwendung von setTimeoutoder setInterval:

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

Was denkst du coffee.amountist?

...

..

.

Es ist immer noch 120. Erstens befanden wir uns innerhalb des coffeeObjekts, da die drinkMethode darin deklariert ist. Wir haben es einfach gemacht setTimeoutund sonst nichts. Das ist genau das.

Wie ich zuvor erklärt habe, wird die setTimeoutMethode tatsächlich im windowObjekt deklariert . Wenn wir es aufrufen, wechseln wir tatsächlich den Kontext wieder in den Kontext window. Das bedeutet, dass unsere Anweisungen tatsächlich versucht haben, sich zu ändern window.amount, aber aufgrund der ifAnweisung nichts unternommen haben. Um das zu erledigen, müssen wir bindunsere Funktionen erfüllen (siehe unten).

Reagieren

Mit React wird dies dank Hooks hoffentlich bald der Vergangenheit angehören. Im Moment müssen wir noch bindalles (dazu später mehr) auf die eine oder andere Weise. Als ich anfing, hatte ich keine Ahnung, warum ich es tat, aber zu diesem Zeitpunkt sollten Sie bereits wissen, warum es notwendig ist.

Schauen wir uns zwei einfache Komponenten der React-Klasse an:

// 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, }) } }

Wenn wir nun auf die von der gerenderte Schaltfläche klicken Child, erhalten wir eine Fehlermeldung. Warum? Weil React beim Aufrufen der _getCoffeeMethode unseren Kontext geändert hat .

Ich gehe davon aus, dass React die Rendermethode unserer Komponenten in einem anderen Kontext durch Hilfsklassen oder ähnliches aufruft (obwohl ich tiefer graben müsste, um es sicher herauszufinden). Daher this.stateist undefiniert und wir versuchen zuzugreifen this.state.coffeeCount. Sie sollten so etwas erhalten Cannot read property coffeeCount of undefined.

Um das Problem zu lösen, müssen Sie binddie Methoden in unseren Klassen anwenden (wir werden sie erreichen), sobald wir sie aus der Komponente herausgeben, in der sie definiert sind.

Schauen wir uns ein weiteres allgemeines Beispiel an:

// 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])

Wir gehen increaseCountvon einer Klasse zur nächsten. Wenn wir die increaseCountMethode aufrufen Viking, haben wir den Kontext bereits geändert und thisverweisen tatsächlich auf das Viking, was bedeutet, dass unsere increaseCountMethode nicht wie erwartet funktioniert.

Lösung - binden

Die einfachste Lösung für uns sind binddie Methoden, die aus unserem ursprünglichen Objekt oder unserer ursprünglichen Klasse übergeben werden. Es gibt verschiedene Möglichkeiten, Funktionen zu binden, aber die häufigste (auch in React) besteht darin, sie im Konstruktor zu binden. Wir müssten diese Zeile also Battlevor Zeile 18 im Konstruktor hinzufügen :

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

Sie können jede Funktion an jeden Kontext binden. Dies bedeutet nicht, dass Sie die Funktion immer an den Kontext binden müssen, in dem sie deklariert ist (dies ist jedoch der häufigste Fall). Stattdessen können Sie es an einen anderen Kontext binden. Mit legenbind Sie immer den Kontext für eine Funktionsdeklaration fest . Dies bedeutet, dass alle Aufrufe für diese Funktion den gebundenen Kontext als erhalten this. Es gibt zwei weitere Helfer zum Festlegen des Kontexts.

Pfeilfunktionen `() => {}` binden die Funktion automatisch an den Deklarationskontext

Bewerben und anrufen

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.