14 metoder til at udskyde eller planlægge JavaScript

Lær hvordan du udskyder og planlægger JavaScript

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2024-11-27

Hvorfor udskyde eller planlægge JavaScript?

JavaScript kan (og vil) gøre din hjemmeside langsommere på flere måder. Dette kan have alle mulige negative konsekvenser for Core Web Vitals. JavaScript kan konkurrere om netværksressourcer, det kan konkurrere om CPU-ressourcer (blokere hovedtråden) og det kan endda blokere parsingen af en webside. At udskyde og planlægge dine scripts kan drastisk forbedre Core Web Vitals.

For at minimere de ubehagelige effekter som JavaScript kan have på Core Web Vitals er det normalt en god idé at specificere hvornår et script sættes i kø til download og planlægge hvornår det kan optage CPU-tid og blokere hovedtråden.

Hvordan kan JavaScript-timing påvirke Core Web Vitals?

Hvordan kan JavaScript-timing påvirke Core Web Vitals? Tag blot et kig på dette eksempel fra den virkelige verden. Den første side er indlæst med 'renderingsblokerende' JavaScript.  Paint-metrikkerne samt Time to interactive er ret dårlige. Det andet eksempel er af præcis den samme side men med JavaScript udskudt. Du vil se at LCP-billedet stadig fik et stort slag. Det tredje eksempel har det samme script eksekveret efter sidens 'load event' og har funktionskaldene opdelt i mindre stykker. Denne sidste passerer Core Web Vitals med margin til overs.

js render blocking lhnull
js deferred lhnull
js after load lhnull


Som standard vil eksternt JavaScript i sidens head blokere oprettelsen af renderingstræet. Mere specifikt: når browseren støder på et script i dokumentet skal den sætte DOM-konstruktionen på pause, overlade kontrollen til JavaScript-runtime'en og lade scriptet køre færdigt før den fortsætter med DOM-konstruktionen. Dette vil påvirke dine Paint-metrikker (Largest Contentful Paint og First Contentful Paint).

Udskudt eller asynkront JavaScript kan stadig påvirke paint-metrikker, især Largest Contentful Paint fordi det vil køre og blokere hovedtråden når DOM'en er blevet oprettet (og almindelige LCP-elementer som måske ikke er blevet downloadet).

Eksterne JavaScript-filer vil også konkurrere om netværksressourcer. Eksterne JavaScript-filer downloades normalt tidligere end billeder. HVIS du downloader for mange scripts, vil downloadingen af dine billeder blive forsinket.

Sidst men ikke mindst kan JavaScript blokere eller forsinke brugerinteraktion. Når et script bruger CPU-ressourcer (blokerer hovedtråden) vil en browser ikke reagere på input (klik, scrolling osv.) indtil det script er færdigt.

Hvordan løser planlægning eller udskydelse af JavaScript Core Web Vitals?

Planlægning eller udskydelse af JavaScript løser ikke Core Web Vitals i sig selv. Det handler om at bruge det rigtige værktøj til den rigtige situation. Som regel bør du forsøge at forsinke dine scripts så lidt som muligt men sætte dem i kø til download og eksekvere dem på det rette tidspunkt.

Hvordan vælger man den rigtige udskydelsesmetode?

Ikke alle scripts er ens og hvert script har sin egen funktionalitet. Nogle scripts er vigtige at have tidligt i renderingsprocessen, andre er det ikke.

js defer vs async vs blockingnull

Jeg kan godt lide at kategorisere JavaScripts i 4 grupper baseret på deres vigtighed.

1. Renderingskritisk. Dette er scripts der vil ændre udseendet af en webside. Hvis de ikke indlæses vil siden ikke se komplet ud. Disse scripts bør undgås for enhver pris. Hvis du ikke kan undgå dem af en eller anden grund bør de ikke udskydes. For eksempel en topslider eller et A/B-testscript.
2. Kritisk. Disse scripts vil ikke ændre udseendet af en webside (for meget) men siden vil ikke fungere godt uden dem. Disse scripts bør udskydes eller gøres asynkrone. For eksempel dine menuscripts.
3. Vigtigt. Dette er scripts du gerne vil indlæse fordi de er værdifulde for dig eller den besøgende. Jeg plejer at indlæse disse scripts efter load-eventet er blevet udløst. For eksempel analytics eller en 'tilbage til toppen'-knap.
4. Rart at have. Dette er scripts du kan leve uden hvis det er absolut nødvendigt. Jeg indlæser disse scripts med den lavest mulige prioritet og eksekverer dem kun når browseren er inaktiv. For eksempel en chat-widget eller en Facebook-knap.

Metode 1: Brug defer-attributten

Scripts med defer-attributten vil downloade parallelt og bliver tilføjet til defer JavaScript-køen. Lige inden browseren udløser DOMContentLoaded-eventet vil alle scripts i den kø blive eksekveret i den rækkefølge de optræder i dokumentet.

<script  src='javascript.js'></script>

'Defer-tricket' løser normalt mange problemer, især paint-metrikkerne. Desværre er der ingen garanti, det afhænger af scriptenes kvalitet. Udskudte scripts vil køre når alle scripts er indlæst og HTML'en er parset (DOMContentLoaded). LCP-elementet (normalt et stort billede) er måske ikke indlæst endnu og de udskudte scripts vil stadig forårsage en forsinkelse i LCP.

Hvornår bruges det:

Brug udskudte scripts til kritiske scripts der er nødvendige hurtigst muligt.

Fordele:

  1. Udskudte scripts vil downloade parallelt
  2. DOM'en vil være tilgængelig på eksekveringstidspunktet

Ulemper:

  1. Udskudte scripts kan forsinke dine LCP-metrikker 
  2. Udskudte scripts vil blokere hovedtråden når de eksekveres
  3. Det er måske ikke sikkert at udskyde scripts når inline- eller asynkrone scripts afhænger af dem

Metode 2: Brug async-attributten

Scripts med async-attributten downloades parallelt og vil køre umiddelbart efter de er færdige med at downloade.

<script  src='javascript.js'></script>

Asynkrone scripts vil gøre lidt for at løse dine pagespeed-problemer. Det er godt at de downloades parallelt men det er stort set det. Når de er downloadet vil de blokere hovedtråden mens de eksekveres.

Hvornår bruges det:

Brug asynkrone scripts til kritiske scripts der er nødvendige hurtigst muligt og er selvstændige (afhænger ikke af andre scripts).

Fordele:

  1. Asynkrone scripts vil downloade parallelt.
  2. Asynkrone scripts vil køre hurtigst muligt.

Ulemper:

  1. DOMContentLoaded kan ske både før og efter async.
  2. Eksekveringsrækkefølgen af scripts vil være ukendt på forhånd.
  3. Du kan ikke bruge asynkrone scripts der afhænger af andre asynkrone eller udskudte scripts

Metode 3: Brug moduler

Modulære scripts er udskudt som standard medmindre de har async-attributten. I det tilfælde vil de blive behandlet som asynkrone scripts

<script  src='javascript.js'></script>

Moduler er en ny måde at tænke JavaScript på og løser nogle designfejl. Udover det vil brug af script-moduler ikke gøre din hjemmeside hurtigere. 

Hvornår bruges det:

Når din applikation er bygget på en modulær måde giver det mening også at bruge JavaScript-moduler.

Fordele:

  1. Moduler er udskudt som standard
  2. Moduler er nemmere at vedligeholde og fungerer godt med modulært webdesign
  3. Moduler muliggør nem kodeopdeling med dynamiske imports hvor du kun importerer de moduler du har brug for på et bestemt tidspunkt.

Ulemper:

  1. Moduler i sig selv vil ikke forbedre Core Web Vitals
  2. Import af moduler just-in-time eller on the fly kan være langsomt og forværre FID og INP 

Metode 4: Placer scripts nær bunden af siden

Footer-scripts sættes i kø til download på et senere tidspunkt. Dette vil prioritere andre ressourcer der er i dokumentet over script-tagget.

<html>
   <head></head>
   <body>
      [your page contents here]
      <script defer src='javascript.js'></script>
   </body>
</html>

At placere dine scripts i bunden af siden er en interessant teknik. Dette vil planlægge andre ressourcer (som billeder) foran dine scripts. Dette vil øge chancen for at de er tilgængelige for browseren og malet på skærmen før JavaScript-filerne er færdige med at downloade og hovedtråden vil blive blokeret af script-eksekvering. Alligevel ... ingen garanti.

Hvornår bruges det:

Når dine scripts allerede præsterer ganske godt men du vil prioritere andre ressourcer som billeder og webfonts en smule.

Fordele:

  1. At placere scripts i bunden af siden kræver ikke megen viden.
  2. Hvis det gøres korrekt er der ingen risiko for at dette vil ødelægge din side

Ulemper:

  1. Kritiske scripts kan blive downloadet og eksekveret senere
  2. Det løser ikke underliggende JavaScript-problemer

Metode 5: Injicer scripts

Injicerede scripts behandles som asynkrone scripts. De downloades parallelt og eksekveres umiddelbart efter download.

<script>
    const loadScript = (scriptSource) => {
        const script = document.createElement('script');
        script.src = scriptSource;
        document.head.appendChild(script);
    }
    
    // call the loadscript function that injects 'javascript.js'
    loadScript('javascript.js');
</script>

Fra et Core Web Vitals-perspektiv er denne teknik præcis det samme som at bruge <script async>.

Hvornår bruges det:

Denne metode bruges ofte af tredjepartsscripts der udløses så tidligt som muligt. Funktionskaldet gør det nemt at indkapsle og komprimere kode.

Fordele:

  1. Indeholdt kode der injicerer et asynkront script.

Ulemper:

  1. DOMContentLoaded kan ske både før og efter scriptet er indlæst.
  2. Eksekveringsrækkefølgen af scripts vil være ukendt på forhånd.
  3. Du kan ikke bruge dette på scripts der afhænger af andre asynkrone eller udskudte scripts

Metode 6: Injicer scripts på et senere tidspunkt

Nice-to-have scripts bør efter min mening aldrig indlæses udskudt. De bør injiceres på det mest gunstige tidspunkt. I eksemplet nedenfor vil scriptet køre efter browseren har sendt 'load'-eventet.

<script>
window.addEventListener('load', function () {
  // see method 5 for the loadscript function
  loadScript('javascript.js');
});
</script>

Dette er den første teknik der pålideligt vil forbedre Largest Contentful Paint. Alle vigtige ressourcer, inklusiv billeder, vil være downloadet når browseren udløser 'load-eventet'. Dette kan introducere alle mulige problemer fordi det kan tage lang tid før load-eventet bliver kaldt.

Hvornår bruges det:

Til nice-to-have scripts der ikke har nogen grund til at påvirke paint-metrikkerne.

Fordele:

  1. Vil ikke konkurrere om kritiske ressourcer fordi det vil injicere scriptet når siden og dens ressourcer er indlæst

Ulemper:

  1. Hvis din side er dårligt designet Core Web Vitals-mæssigt kan det tage lang tid for siden at sende 'load'-eventet
  2. Du skal passe på ikke at anvende dette på kritiske scripts (som lazy loading, menu osv.)

Metode 7: Ændr script-typen (og ændr den tilbage igen)

Hvis et script-tag findes et sted på siden der 1. har en type-attribut og 2. type-attributten ikke er "text/javascript" vil scriptet ikke blive downloadet og eksekveret af en browser. Mange JavaScript-loadere (som CloudFlares RocketLoader) er baseret på dette princip. Idéen er ganske simpel og elegant.

Først omskrives alle scripts som dette:

<script  src="javascript.js"></script>

Derefter, på et tidspunkt under indlæsningsprocessen, konverteres disse scripts tilbage til 'normale javascripts'.

Hvornår bruges det:

Dette er ikke en metode jeg vil anbefale. At rette JavaScript-påvirkning kræver meget mere end bare at flytte hvert script lidt længere ned i køen. På den anden side, hvis du har lidt kontrol over de scripts der kører på siden eller har utilstrækkelig JavaScript-viden, kan dette være dit bedste bud.

Fordele:

  1. Det er nemt, aktiver bare rocket loader eller et andet plugin og alle dine scripts eksekveres nu på et lidt senere tidspunkt.
  2. Det vil sandsynligvis løse dine paint-metrikker forudsat at du ikke brugte JS-baseret lazy loading.
  3. Det virker for inline og eksterne scripts.

Ulemper:

  1. Du vil ikke have finkornet kontrol over hvornår scripts eksekveres
  2. Dårligt skrevne scripts kan gå i stykker
  3. Det bruger JavaScript til at rette JavaScript
  4. Det gør intet for at rette langtidskørende scripts

Metode 8: Brug intersection observer

Med intersection observer kan du eksekvere en funktion (som i dette tilfælde indlæser et eksternt JavaScript) når et element scroller ind i det synlige viewport.

<script>
const handleIntersection = (entries, observer) => {
    if (entries?.[0].isIntersecting) {
        // load your script or execute another 
           function like trigger a lazy loaded element
        loadScript('javascript.js');

        // remove the observer
        observer.unobserve(entries?.[0].target);
    }
};
const Observer = new window.IntersectionObserver()
Observer.observe(document.querySelector('footer'));
</script>

Dette er langt den mest effektive metode til at udskyde JavaScript der findes. Indlæs kun de scripts du har brug for, lige inden du har brug for dem. Desværre er virkeligheden sjældent så ren og ikke mange scripts kan bindes til et element der scroller ind i viewet.

Hvornår bruges det:

Brug denne teknik så meget som muligt! Når et script kun interagerer med en komponent uden for skærmen (som et kort, en slider, en formular) er dette den bedste måde at injicere dette script.

Fordele:

  1. Vil ikke forstyrre Core Web Vitals LCP og FCP
  2. Vil aldrig injicere scripts der ikke bruges. Dette vil forbedre FID og INP

Ulemper:

  1. Bør ikke bruges med komponenter der kan være i det synlige viewport
  2. Er sværere at vedligeholde end grundlæggende <script src="...">
  3. Kan introducere et layout shift

Metode 9: Brug readystatechange

document.readystate kan bruges som et alternativ til 'DOMContentloaded'- og 'load'-eventet. Den 'interactive' readystate er normalt et godt sted at kalde kritiske scripts der skal ændre DOM'en eller tilføje eventhandlere. 
Den 'complete' readystate er et godt sted at kalde scripts der er mindre kritiske.

document.addEventListener('readystatechange', (event) => {
  if (event.target.readyState === 'interactive') {
    initLoader();
  } else if (event.target.readyState === 'complete') {
    initApp();
  }
});

Metode 10: Brug setTimeout uden timeout

setTimeout er en ildeset men stærkt undervurderet metode i pagespeed-fællesskabet. setTimeout har fået et dårligt ry fordi den ofte misbruges.  Mange udviklere tror at setTimeout kun kan bruges til at forsinke script-eksekvering med det indstillede antal millisekunder. Selvom dette er sandt gør setTimeout faktisk noget langt mere interessant. Den opretter en ny opgave i slutningen af browserens event loop. Denne adfærd kan bruges til at planlægge dine opgaver effektivt. Den kan også bruges til at opdele lange opgaver i separate mindre opgaver

<script>
   setTimeout(() => {
       // load a script or execute another function
       console.log('- I am called from a 0ms timeOut()')
    }, 0);

   console.log('- I was last in line but executed first')
   /*
      Output:
      - I was last in line but executed first
      - I am called from a 0ms timeOut()
   */
</script>

Hvornår bruges det:

setTimeout opretter en ny opgave i browserens event loop. Brug dette når din hovedtråd blokeres af mange funktionskald der kører sekventielt.

Fordele:

  1. Kan opdele langtidskørende kode i mindre stykker.

Ulemper:

  1. setTimeout er en ret grov metode og tilbyder ikke prioritering af vigtige scripts.
  2. Vil tilføje den kode der skal eksekveres i slutningen af loopet

Metode 11: Brug setTimeout med en timeout

Det bliver endnu mere interessant når vi kalder setTimeout med en timeout på mere end 0ms

<script>
   setTimeout(() => {
       // load a script or execute another function
       console.log('- I am called from a 10ms timeOut()')
    }, 10);

   setTimeout(() => {
       // load a script or execute another function
       console.log('- I am called from a 0ms timeOut()')
    }, 0);

   console.log('- I was last in line but executed first')
   /*
      Output:
      - I was last in line but executed first
      - I am called from a 0ms timeOut()
      - I am called from a 10ms timeOut()
   */
</script>

Hvornår bruges det:

Når du har brug for en nem metode til at planlægge ét script efter et andet vil en lille timeout gøre det

Fordele:

  1. Understøttet i alle browsere

Ulemper:

  1. Tilbyder ikke avanceret planlægning

Metode 12: Brug et promise til at sætte en microtask

At oprette en micro-task er også en interessant måde at planlægge JavaScript på. Micro-tasks planlægges til eksekvering umiddelbart efter den nuværende eksekveringsløkke er afsluttet.

<script>
   const myPromise = new Promise((resolve, reject) => {
      resolve();
   }).then(
      () => {       
         console.log('- I was scheduled after a promise')
         }
   );   
   console.log('- I was last in line but executed first')

   /*
      Output:
      - I was last in line but executed first
      - I was scheduled after a promise
   */
</script>

Hvornår bruges det:

Når en opgave skal planlægges umiddelbart efter en anden opgave. 

Fordele:

  1. Microtask'en vil blive planlagt umiddelbart efter opgaven er færdig med at køre.
  2. En microtask kan bruges til at forsinke mindre vigtige dele af JavaScript-kode i det samme event. 

Ulemper:

  1. Vil ikke opdele hovedtråden i mindre dele. Browseren vil ikke have mulighed for at reagere på brugerinput.
  2. Du vil sandsynligvis aldrig have brug for at bruge microtasks til at forbedre Core Web Vitals medmindre du allerede ved præcis hvad du laver.

Metode 13: Brug en microtask

Det samme resultat kan opnås ved at bruge queueMicrotask(). Fordelen ved at bruge queueMicrotask() frem for et promise er at det er lidt hurtigere og du behøver ikke håndtere dine promises.

<script>
   queueMicrotask(() => {
      console.log('- I am a microtask')
   })
     
   console.log('- I was last in line but executed first')

   /*
      Output:
      - I was last in line but executed first
      - I am a microtask
   */
</script>


Metode 14: Brug requestIdleCallback

Metoden window.requestIdleCallback() sætter en funktion i kø til at blive kaldt under en browsers inaktive perioder. Dette gør det muligt for udviklere at udføre baggrunds- og lavprioritetsarbejde på hovedeventløkken uden at påvirke latenskritiske hændelser som animation og inputrespons. Funktioner kaldes generelt i først-ind-først-ud-rækkefølge; dog kan callbacks der har en timeout specificeret blive kaldt i en anden rækkefølge hvis det er nødvendigt for at køre dem inden timeout'en udløber.

<script>
requestIdleCallback(() => {
    const script = document.createElement('script');
    script.src = 'javascript.js';
    document.head.appendChild(script);
});
</script>

Hvornår bruges det:

Brug dette til scripts der er rart at have eller til at håndtere ikke-kritiske opgaver efter brugerinput

Fordele:

  1. Eksekver JavaScript med minimal påvirkning for brugeren
  2. Vil højst sandsynligt forbedre FID og INP

Ulemper:

  1. Understøttet i de fleste browsere men ikke alle. Der findes polyfills der falder tilbage på setTimeout();
  2. Ingen garanti for at koden nogensinde vil køre 

Metode 15: Brug postTask

Metoden tillader brugere valgfrit at specificere en minimumsforsinkelse før opgaven køres, en prioritet for opgaven og et signal der kan bruges til at ændre opgavens prioritet og/eller afbryde opgaven. Den returnerer et promise der bliver resolved med resultatet af opgavens callback-funktion eller rejected med afbrydelsesårsagen eller en fejl kastet i opgaven.

<script>
scheduler.postTask(() => {
    const script = document.createElement('script');
    script.src = 'javascript.js';
    document.head.appendChild(script);
}, { priority: 'background' });
</script>

Hvornår bruges det:

postTask er det perfekte API til at planlægge scripts med. Desværre er browserunderstøttelsen på nuværende tidspunkt dårlig så du bør ikke bruge det.

Fordele:

  1. Komplet kontrol over JavaScript-eksekveringsplanlægning!

Ulemper:

  1. Ikke understøttet i nogle vigtige browsere.

Make decisions with Data.

You cannot optimize what you do not measure. Install the CoreDash pixel and capture 100% of user experiences.

Create Free Account >>

  • 100% Capture
  • Data Driven
  • Easy Install
14 metoder til at udskyde eller planlægge JavaScriptCore Web Vitals 14 metoder til at udskyde eller planlægge JavaScript