JavaScript is in tien dagen gemaakt
10 april 2023
Brendan Eich maakte JavaScript in tien dagen. Het begon als een manier om interactiviteit aan de browser toe te voegen, in feite hackergereedschap, en nu is het de populairste programmeertaal ter wereld. Net als het Engels is het gebruik divers en past het zich voortdurend aan nieuwe situaties aan.
Ik wil die veelzijdigheid in één enkel woord traceren: this.
Kom je uit een andere objectgeoriënteerde taal en ga je serieuzer JavaScript gebruiken, dan kom je op enig moment de eigenaardigheid van this tegen. Het verandert van context. Het wordt op runtime bepaald, wat impliciet of expliciet kan worden vastgelegd, afhangt van de stijl waarin een functie is geschreven, en van de vraag of je in strict-mode draait. Het verwijst niet altijd terug naar de instantie van het object.
Dit gedrag maakte DOM-manipulatie in de jaren 90 eenvoudiger, en jQuery leunt er zwaar op. Toch bracht het me in de begintijd van React behoorlijk in de war. Beschouw dit voorbeeld:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this); // logt de component-instance
}
render() {
return <button>Click me</button>;
}
}Voor React functionele componenten en hooks introduceerde, moest een component de React.Component-klasse uitbreiden om gebruik te maken van lifecycle-methoden. Een veelvoorkomend geval is een event listener toevoegen nadat het DOM-element is gerenderd, of die weer verwijderen na unmount. In plaats van alles in een render-methode te dumpen, die voor renderen verantwoordelijk hoort te zijn, was het logisch de event handler in een eigen methode te zetten.
Maar daar zit hem de kneep: de render-component maakt een DOM-element aan, dat door interactie van de gebruiker een event op de wachtrij zet. De context van this op dat moment is niet de React-component die het DOM-element renderde, maar het DOM-element zelf. Daarom is een expliciete binding aan de component nodig:
this.handleClick = this.handleClick.bind(this); Dit is zo contra-intuïtief dat het lange en verhitte discussies heeft opgeleverd over de vraag of JavaScript überhaupt een objectgeoriënteerde taal genoemd kan worden. Het algemene oordeel is volgens mij "ja", maar ik ben geen autoriteit. JS ondersteunt objecten, klassen, overerving, encapsulatie en polymorfisme. Mozilla noemt JavaScript zelfs "heavily object oriented". Zelfs functies zijn in JavaScript een soort object.
Maar this probeerde alleen behulpzaam te zijn.
ES6 en hooks
Taal beweegt sneller dan modellering, in zowel het gesproken woord als in software. Sinds hooks in React 16.8 zijn geïntroduceerd is de industriestandaard het schrijven van functionele componenten:
import React from 'react';
function MyComponent() {
return (
<button => console.log(this)}>Click me</button>
);
}In dit nieuwe voorbeeld logt this als undefined, niet als het DOM-element. Dat heeft niets te maken met de nieuwe functionaliteit van React, maar met een eigenschap van ES6. De onClick krijgt een arrow function expression mee, en die heeft geen eigen this-context. Voor dit gebruik maakt dat de zaak veel beter leesbaar. Dat is dus een voorbeeld van een evoluerende taalspecificatie.
Maar de evolutie van het gebruik, in dit geval rond de introductie van hooks, vind ik net zo interessant. Blogposts over hooks richten zich vaak op herbruikbaarheid of beknoptheid als reden waarom hooks "geweldig" zijn. Door bij functies te blijven, hoeft een ontwikkelaar minder te weten en kan zij zich richten op het renderen van interfacecomponenten. We hebben geen wereld nodig vol werkende ontologen.
Respecteer de "domme"
Soepel gesprek in het dagelijks leven zit vol met aannames. Schrijf ik een script om een knop-element te vinden, en wil ik dat "deze" na een klik gedeactiveerd wordt, dan verwacht ik dat de interpreter snapt wat ik bedoel. Aannames maken een ezel van jou en mij, zegt men, maar die mensen hebben rijke vaders en geen deadlines.
React heeft zijn populariteit voor een groot deel te danken aan een zekere gradatie (in plaats van scheiding) van verantwoordelijkheden, van de "slimmere" datacomponenten die fetchen, routing en state-management voor hun rekening nemen en grotendeels uniek per applicatie zijn, tot de "domme" componenten die de gebruikersinterface renderen. Dat spectrum van slim naar dom gaat niet over de intelligentie of vaardigheid van de ontwikkelaar, maar over de mate van dynamische context in een component. De schaalbaarheid en populariteit van React zit in die domme componenten. Context is expliciet in de vorm van props, totdat dat niet meer zo is. En dan word ik kritisch.