Hopp til innhold

JavaScript

språket nettlesere kjører

Hva det er

JavaScript er et dynamisk typet programmeringsspråk, standardisert som ECMAScript, som enhver nettleser kan kjøre, og som også kjører på servere og byggemaskiner gjennom kjøremiljøer som Node.js. Mange forkorter det til JS. Standardens årlige utgaver heter ES2015, ES2024 og så videre, den eldre nummereringen (ES5, ES6) høres fortsatt, og «vanilla JavaScript» betyr språket uten noe rammeverk oppå. Bortsett fra navnet og en del syntaks med krøllparenteser har det lite til felles med Java.

Brendan Eich skrev den første versjonen hos Netscape, og den ble levert med nettleseren Navigator 2.0. Ecma International vedtok den første utgaven av standarden, ECMA-262, i juni 1997, og komiteen TC39 har publisert en ny utgave hvert år siden ECMAScript 2016. Et forslag kommer inn i spesifikasjonen først på trinn 4 i komiteens prosess, og det trinnet krever implementasjoner som allerede er levert, så teksten og motorene er sjelden uenige lenge.

Språket sier svært lite om verden utenfor. ECMA-262 definerer syntaksen, typene, objekter, promises og moduler, og overlater inndata og utdata til det miljøet som er vert for språket. I en nettleser er den verten webplattformen: dokumentet, hendelser, tidtakere og nettverksforespørsler, spesifisert for det meste i HTML-standarden og søsterstandardene dens. I Node.js leverer verten i stedet filer, sockets og prosesser. Det skillet forklarer en vanlig overraskelse: kode kan være helt gyldig JavaScript og likevel slutte å virke når den flyttes fra en side til en server, fordi språket ble med og verten ble igjen.

I en nettleser deler skriptene på en side én hovedtråd med layout, tegning og inndata. En oppgave kjører ferdig før nettleseren kan svare på neste klikk. Promises og async-funksjoner holder ventingen utenfor den tråden, mens beregninger blir værende på den med mindre de flyttes til en Web Worker. Tall er flyttall med dobbel presisjon etter IEEE 754, så heltall er eksakte bare opp til 2^53 − 1, og alt større krever BigInt.

Hvorfor det er viktig

Den egentlige avgjørelsen er hvor mye av det du skal sende til dem som leser nettstedet ditt. JavaScript er språket som får en side til å oppføre seg uten en rundtur til en server, og hver kilobyte av det lastes ned, parses, kompileres og kjøres på en enhet du ikke har kontroll over. Pakken som føles øyeblikkelig på en utviklers bærbare maskin, kan holde en mellomklassetelefon opptatt i flere sekunder, og leseren betaler for det med batteri og tålmodighet.

Skript er også der en side er mest utsatt. Alt som injiseres i det, kjører med sidens fulle myndighet: det kan lese det leseren skriver, og sende det hvor som helst siden har lov til å sende noe. Det er den risikoen Content Security Policy finnes for å begrense, og en side uten skript har ingenting å injisere i. Pakkeøkosystemet legger til en eksponering til. Et typisk bygg av en frontend trekker inn hundrevis av transitive avhengigheter, og hver av dem er kode som kjører med de samme rettighetene som din egen.

Den andre siden av regnskapet er reell. Ett språk på begge sider av et system lar nettleseren og serveren dele en valideringsregel eller en dataform, og noen produkter trenger mye skript: tilstand uten nett, dra og slipp, kart, samarbeid i sanntid. Det nyttige spørsmålet stilles per funksjon: må dette skje i leserens nettleser uten å spørre en server? Hvis ikke, gjengi det på serveren eller ved byggetid og send HTML.

Det beslektede valget er JavaScript mot TypeScript. TypeScript legger til en typekontroll og et byggesteg, og kompileres tilbake til ren JavaScript. For en kodebase som flere personer endrer hver uke, betaler typekontrollen som regel for seg selv; for en forbedring på hundre linjer på en statisk side holder det med JavaScript og en streng linter.

Slik ser det ut i praksis

Dette nettstedet sender nesten ingen JavaScript til leserne sine og kjører svært mye av det ved byggetid. Tre sider viser begge halvdelene av det.

Det offentlige nettstedet er et statisk bygg på tre språk uten noen backend ved kjøretid, og nettleseren mottar ett eneste førstepartsskript. Det skriptet er et avgrenset unntak for én funksjon, et porteføljefilter der kontrollene holdes skjult til skriptet har kjørt. Resten av nettstedet, 108 malfiler med 53 partials og 18 shortcodes, gjør jobben sin når nettstedet bygges. Siden skriver også ned prisen for det valget: alt interaktivt må løses ved byggetid, og det er grunnen til at en søkeindeks ble kostnadsberegnet og lagt på is i stedet for levert.

Det samme innholdet publiseres over Gemini og Gopher, to protokoller uten plass til skript i det hele tatt. Bygget produserer 777 Gemini-dokumenter og 777 Gopher-menyer fra den samme kilden, og en måling bekreftet at HTML-en ikke endret seg med en eneste byte. De to alternative formatene legger omtrent 12 % til vekten av HTML-en på disk. Nettstedet argumenterer for at et dokument ikke trenger en megabyte skript for å kunne leses, og et format som ikke kan romme skript, er den enkleste måten å vise det på.

Det meste av JavaScript-koden her ligger i verktøyene. Kvalitetsporten på nettstedet er trettito skript og 10 242 linjer JavaScript, den største kodemengden i repoet, og en tid var den den eneste kodemengden som ingenting formaterte, lintet eller typekontrollerte. Å få den under en formaterer og en linter begynte med å tilpasse formatereren til koden: repoets standard på fire mellomrom ville ha skrevet om 2 173 linjer i det største kontrollskriptet, og en overstyring til to mellomrom for de filene etterlot 38 linjer med reelle avvik. Linteren ga deretter 13 funn, og alle 13 ble rettet, ingen undertrykt. En feil i et kontrollskript viser seg som en kontroll som består når den burde feile, så portens egen kode får den samme behandlingen som den gir alt annet.

Hvor det går galt

Feilene nedenfor følger av hvordan språket er bygd, og hver av dem har en kjent løsning.

Siden tegnes, men ignorerer trykk i et sekund eller mer. En lang oppgave, som å hydrere en stor kodebunt, parse et stort JSON-svar eller sortere tusenvis av rader, holder den ene tråden som håndterer inndata. Send mindre kode for den første interaksjonen ved å dele kodebuntene per rute og utsette resten, del opp langt arbeid i biter som gir kontrollen tilbake til hendelsesløkken, og flytt tunge beregninger til en Web Worker. Mål på en treg telefon og ikke på den bærbare maskinen koden ble skrevet på.

Priser og identifikatorer kommer ut litt feil. Hvert tall er et binært flyttall, så desimalbrøker rundes av, og svært store heltall kollapser inn i naboene sine. En 64-biters databasenøkkel sendt som et JSON-tall er det vanlige offeret:

0.1 + 0.2 === 0.3; // false
Number.MAX_SAFE_INTEGER; // 9007199254740991
9007199254740993 === 9007199254740992; // true

Hold pengebeløp i heltall av den minste enheten, som øre eller cent, send store identifikatorer som strenger, og bruk BigInt der regnestykket må være eksakt.

En sammenligning slipper gjennom feil verdi. Operatoren == konverterer operandene før den sammenligner dem, så '' == 0 og '0' == false er begge sanne. Bruk === overalt og la regelen eqeqeq i linteren håndheve det, slik at konverteringen blir noe koden ber uttrykkelig om.

Tekst fra en bruker ender opp med å kjøre som kode. Å tilordne ubetrodd tekst til innerHTML, bygge markup ved å skjøte sammen strenger eller sende inndata til eval gjør data om til skript. Skriv tekst med textContent, send en Content Security Policy som bare slipper inn skript med nonce eller hash, og hold avhengighetslisten kort og låst i en låsefil, og les hva hver oppdatering trekker inn.

Kode som virker i nettleseren, krasjer på serveren. Språket flyttet seg, men verten gjorde det ikke: window og document finnes bare i en nettleser, og Node.js leser en fil som CommonJS eller som en ES-modul avhengig av innstillinger nettleseren aldri ser. Hold vertsspesifikke kall i kantene, sett feltet type til module eksplisitt i package.json, og kjør hver kodebit i det miljøet den faktisk skal kjøre i.

Les mer

Ordliste