window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(funktion () { var w = vindue if (w.LeadBooster) { console.warn('LeadBooster findes allerede') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() Et par tricks til at gøre din JavaScript-applikation hurtigere - The Codest
Codest
  • Om os
  • Serviceydelser
    • Udvikling af software
      • Frontend-udvikling
      • Backend-udvikling
    • Staff Augmentation
      • Frontend-udviklere
      • Backend-udviklere
      • Dataingeniører
      • Cloud-ingeniører
      • QA-ingeniører
      • Andet
    • Det rådgivende
      • Revision og rådgivning
  • Industrier
    • Fintech og bankvirksomhed
    • E-commerce
    • Adtech
    • Sundhedsteknologi
    • Produktion
    • Logistik
    • Biler
    • IOT
  • Værdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leder af levering
  • Vores team
  • Casestudier
  • Ved hvordan
    • Blog
    • Møder
    • Webinarer
    • Ressourcer
Karriere Tag kontakt til os
  • Om os
  • Serviceydelser
    • Udvikling af software
      • Frontend-udvikling
      • Backend-udvikling
    • Staff Augmentation
      • Frontend-udviklere
      • Backend-udviklere
      • Dataingeniører
      • Cloud-ingeniører
      • QA-ingeniører
      • Andet
    • Det rådgivende
      • Revision og rådgivning
  • Værdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leder af levering
  • Vores team
  • Casestudier
  • Ved hvordan
    • Blog
    • Møder
    • Webinarer
    • Ressourcer
Karriere Tag kontakt til os
Pil tilbage GÅ TILBAGE
2020-11-25
Udvikling af software

Et par tricks til at gøre din JavaScript-applikation hurtigere

Codest

Bartosz Slysz

Software Engineer

Med udviklingen af browserteknologi er webapplikationer begyndt at overføre mere og mere logik til frontend, hvilket aflaster serveren og reducerer antallet af operationer, den skal udføre. I grundlæggende CRUD'er kommer serverens rolle til at handle om autorisation, validering, kommunikation med databaser og den nødvendige forretningslogik. Resten af datalogikken kan nemt håndteres af den kode, der er ansvarlig for repræsentationen af applikationen på UI-siden.

I denne artikel vil jeg forsøge at vise dig et par eksempler og mønstre, der kan hjælpe med at holde vores Kode effektivt, pænt og hurtigt.

Før vi går dybere ind i specifikke eksempler - i denne artikel vil jeg kun fokusere på at vise tilfælde, der efter min mening kan påvirke applikationens hastighed på en overraskende måde. Det betyder dog ikke, at brugen af hurtigere løsninger er det bedste valg i alle mulige tilfælde. Nedenstående tips skal snarere ses som noget, man bør undersøge, når vores applikation kører langsomt, f.eks. i produkter, der kræver spilrendering eller mere avancerede grafer på lærredet, videooperationer eller aktiviteter, som man ønsker at synkronisere i realtid så hurtigt som muligt.

Først og fremmest - Array.prototype-metoder

Vi baserer en stor del af applikationslogikken på arrays - deres mapping, sortering, filtrering, summering af elementer og så videre. På en nem, gennemsigtig og naturlig måde bruger vi deres indbyggede metoder, som ganske enkelt giver os mulighed for at udføre forskellige typer beregninger, grupperinger osv. De fungerer på samme måde i hvert tilfælde - som argument sender vi en funktion, hvor elementværdien, indekset og arrayet i de fleste tilfælde skubbes på skift under hver iteration. Den angivne funktion udføres for hvert element i arrayet, og resultatet fortolkes forskelligt afhængigt af metoden. Jeg vil ikke uddybe Array.prototypens metoder, da jeg vil fokusere på, hvorfor den kører langsomt i mange tilfælde.

Array-metoderne er langsomme, fordi de udfører en funktion for hvert element. En funktion, der kaldes fra motorens perspektiv, skal forberede et nyt kald, give det rette scope og en masse andre afhængigheder, hvilket gør processen meget længere end at gentage en bestemt kodeblok i et bestemt scope. Og det er nok baggrundsviden nok til at forstå det følgende eksempel:

(() => {
const randomArray = [...Array(1E6).keys()].map(() => ({ value: Math.random() }));

console.time('Sum ved at reducere');
const reduceSum = randomArray
    .map(({ value }) => value)
    .reduce((a, b) => a + b);
console.timeEnd('Sum ved at reducere');

console.time('Sum ved for-løkke');
let forSum = randomArray[0].value;
for (let index = 1; index < randomArray.length; index++) {
    forSum += randomArray[index].value;
}
console.timeEnd('Sum by for loop');

console.log(reduceSum === forSum);

})();

Jeg ved godt, at denne test ikke er lige så pålidelig som benchmarks (dem vender vi tilbage til senere), men den udløser en advarselslampe. I et tilfældigt tilfælde på min computer viser det sig, at koden med for-løkken kan være ca. 50 gange hurtigere sammenlignet med at kortlægge og derefter reducere elementer, der opnår samme effekt! Det handler om at operere på et mærkeligt objekt, der kun er skabt for at nå et bestemt mål for beregninger. Så lad os skabe noget mere legitimt for at være objektive omkring Array-metoderne:

(() => {
const randomArray = [...Array(1E6).keys()].map(() => ({ value: Math.random() }));

console.time('Sum ved at reducere');
const reduceSum = randomArray
    .reduce((a, b) => ({ value: a.value + b.value })).value
console.timeEnd('Sum ved at reducere');

console.time('Sum ved for-loop');
let forSum = randomArray[0].value;
for (let index = 1; index < randomArray.length; index++) {
    forSum += randomArray[index].value;
}
console.timeEnd('Sum by for loop');

console.log(reduceSum === forSum);

})();

Jeg ved godt, at denne test ikke er lige så pålidelig som benchmarks (dem vender vi tilbage til senere), men den udløser en advarselslampe. I et tilfældigt tilfælde på min computer viser det sig, at koden med for-løkken kan være ca. 50 gange hurtigere sammenlignet med at kortlægge og derefter reducere elementer, der opnår den samme effekt! Det skyldes, at det at få summen i dette særlige tilfælde ved hjælp af reduce-metoden kræver, at man mapper arrayet for rene værdier, som vi ønsker at opsummere. Så lad os skabe noget mere legitimt for at være objektive omkring Array-metoderne:

(() => {
const randomArray = [...Array(1E6).keys()].map(() => ({ value: Math.random() }));

console.time('Sum ved at reducere');
const reduceSum = randomArray
    .reduce((a, b) => ({ value: a.value + b.value })).value
console.timeEnd('Sum ved at reducere');

console.time('Sum ved for-loop');
let forSum = randomArray[0].value;
for (let index = 1; index < randomArray.length; index++) {
    forSum += randomArray[index].value;
}
console.timeEnd('Sum by for loop');

console.log(reduceSum === forSum);

})();

Og det viste sig, at vores 50x boost blev reduceret til 4x boost. Undskyld, hvis du føler dig skuffet! Lad os analysere begge koder igen for at forblive objektive til sidst. Først og fremmest - uskyldige forskelle fordoblede faldet i vores teoretiske beregningskompleksitet; i stedet for først at mappe og derefter lægge rene elementer sammen, opererer vi stadig med objekter og et specifikt felt for til sidst at trække det ud for at få den sum, vi er interesseret i. Problemet opstår, når en anden programmør kigger på koden - så mister sidstnævnte sin abstraktion på et eller andet tidspunkt sammenlignet med de koder, der blev vist tidligere.

Det skyldes, at vi siden den anden operation opererer på et mærkeligt objekt med det felt, der er interessant for os, og det andet standardobjekt i det itererede array. Jeg ved ikke, hvad du synes om det, men fra mit perspektiv er logikken i for-løkken i det andet kodeeksempel meget klarere og mere hensigtmæssig end denne mærkelige reduktion. Og selv om det ikke længere er de mytiske 50, er det stadig 4 gange hurtigere, når det gælder beregningstid! Da hvert millisekund er værdifuldt, er valget i dette tilfælde enkelt.

Det mest overraskende eksempel

Den anden ting, jeg gerne ville sammenligne, er Math.max-metoden, eller mere præcist, at fylde en million elementer og udtrække de største og mindste. Jeg har forberedt koden og metoderne til at måle tiden, og så starter jeg koden og får en meget mærkelig fejl - stakstørrelsen er overskredet. Her er koden:

(() => {
const randomValues = [...Array(1E6).keys()].map(() => Math.round(Math.random() * 1E6) - 5E5);

console.time('Math.max med ES6 spread operator');
const maxBySpread = Math.max(...randomValues);
console.timeEnd('Math.max med ES6 spredningsoperator');

console.time('Math.max med for-løkke');
let maxByFor = randomValues[0];
for (let index = 1; index  maxByFor) {
        maxByFor = randomValues[index];
    }
}
console.timeEnd('Math.max med for-løkke');

console.log(maxByFor === maxBySpread);

})();
(() => {
const randomValues = [...Array(1E6).keys()].map(() => Math.round(Math.random() * 1E6) - 5E5);

console.time('Math.max med ES6 spread operator');
const maxBySpread = Math.max(...randomValues);
console.timeEnd('Math.max med ES6 spredningsoperator');

console.time('Math.max med for-løkke');
let maxByFor = randomValues[0];
for (let index = 1; index  maxByFor) {
        maxByFor = randomValues[index];
    }
}
console.timeEnd('Math.max med for-løkke');

console.log(maxByFor === maxBySpread);

})();

Det viser sig, at native metoder bruger rekursion, som i v8 er begrænset af call stacks, og antallet er afhængigt af miljøet. Det er noget, der overraskede mig meget, men det har en konklusion: Den oprindelige metode er hurtigere, så længe vores array ikke overstiger et vist magisk antal elementer, som i mit tilfælde viste sig at være 125375. For dette antal elementer var resultatet for 5x hurtigere sammenlignet med løkken. Men over det nævnte antal elementer vinder for-løkken helt sikkert - i modsætning til modstanderen giver den os mulighed for at få korrekte resultater.

Rekursion

Det koncept, jeg vil nævne i dette afsnit, er rekursion. I det foregående eksempel så vi det i Math.max-metoden og argumentfoldningen, hvor det viste sig, at det er umuligt at få et resultat for rekursive kald, der overstiger et bestemt tal, på grund af begrænsningen i stakkens størrelse.

Vi vil nu se, hvordan rekursion ser ud i forbindelse med kode skrevet i JS og ikke med indbyggede metoder. Den måske mest klassiske ting, vi kan vise her, er selvfølgelig at finde det n'te udtryk i Fibonacci-rækken. Så lad os skrive det!

(() => {
const fiboIterative = (n) => {
lad [a, b] = [0, 1];

    for (let i = 0; i  {
    if(n < 2) {
        returnerer n;
    }

    return fiboRecursive(n - 2) + fiboRecursive(n - 1);
};

console.time('Fibonacci-sekvens med for-loop');
const resultIterative = fiboIterative(30);
console.timeEnd('Fibonacci-sekvens ved for loop');

console.time('Fibonacci-sekvens ved rekursion');
const resultRecursive = fiboRecursive(30);
console.timeEnd('Fibonacci-sekvens ved rekursion');

console.log(resultRecursive === resultIterative);

})();

Okay, i dette særlige tilfælde, hvor vi beregner det 30. element i sekvensen på min computer, får vi resultatet på ca. 200 gange kortere tid med den iterative algoritme.

Der er dog en ting, der kan rettes op på i den rekursive algoritme - det viser sig, at den fungerer meget mere effektivt, når vi bruger en taktik, der kaldes tail recursion. Det betyder, at vi sender det resultat, vi fik i den forrige iteration, videre som argumenter for dybere kald. Det giver os mulighed for at reducere antallet af nødvendige kald og dermed fremskynde resultatet. Lad os rette vores kode i overensstemmelse hermed!

(() => {
const fiboIterative = (n) => {
lad [a, b] = [0, 1];

    for (let i = 0; i  {
    if(n === 0) {
        returnerer første;
    }

    return fiboTailRecursive(n - 1, second, first + second);
};

console.time('Fibonacci-sekvens med for-loop');
const resultIterative = fiboIterative(30);
console.timeEnd('Fibonacci-sekvens ved for loop');

console.time('Fibonacci-sekvens ved hale-rekursion');
const resultRecursive = fiboTailRecursive(30);
console.timeEnd('Fibonacci-sekvens ved hale-rekursion');

console.log(resultRecursive === resultIterative);

})();

Der skete noget, som jeg ikke helt havde forventet - resultatet af hale-rekursionsalgoritmen var i stand til at levere resultatet (beregning af det 30. element i en sekvens) næsten dobbelt så hurtigt som den iterative algoritme i nogle tilfælde. Jeg er ikke helt sikker på, om det skyldes optimering af tail recursion fra v8's side eller den manglende optimering af for-loopen til dette specifikke antal iterationer, men resultatet er entydigt - tail recursion vinder.

Det er underligt, fordi for-løkken i bund og grund pålægger beregningsaktiviteter på lavere niveau meget mindre abstraktion, og man kan sige, at den er tættere på den grundlæggende computeroperation. Alligevel er resultaterne ubestridelige - smart designet rekursion viser sig at være hurtigere end iteration.

Brug asynkrone call statements så ofte som muligt

Jeg vil gerne bruge det sidste afsnit til en kort påmindelse om en metode til at udføre operationer, som også i høj grad kan påvirke hastigheden af vores program. Som du bør vide, JavaScript er et single-threaded sprog, der udfører alle operationer med en event-loop-mekanisme. Det handler om en cyklus, der kører igen og igen, og alle trin i denne cyklus handler om dedikerede, specificerede handlinger.

For at gøre dette loop hurtigt og lade alle cyklusser vente mindre på deres tur, skal alle elementer være så hurtige som muligt. Undgå at køre lange operationer på hovedtråden - hvis noget tager for lang tid, så prøv at flytte disse beregninger til WebWorker eller opdel dem i dele, der kører asynkront. Det kan gøre nogle operationer langsommere, men øger hele JS' økosystem, herunder IO-operationer, såsom håndtering af musebevægelser eller ventende HTTP-anmodninger.

Sammenfatning

Som nævnt tidligere kan jagten på millisekunder, der kan spares ved at vælge en algoritme, i nogle tilfælde vise sig at være meningsløs. På den anden side kan det være dødbringende for din applikation at negligere sådanne ting i applikationer, der kræver jævn kørsel og hurtige resultater. I nogle tilfælde bør man ud over algoritmens hastighed også stille et andet spørgsmål: Er abstraktionen på det rigtige niveau? Vil den programmør, der læser koden, kunne forstå den uden problemer?

Den eneste måde er at sikre balancen mellem ydeevne, nem implementering og passende abstraktion og være sikker på, at algoritmen fungerer korrekt for både små og store datamængder. Måden at gøre det på er ret enkel - vær smart, overvej de forskellige tilfælde, når du designer algoritmen, og sørg for, at den opfører sig så effektivt som muligt ved gennemsnitlige udførelser. Det er også tilrådeligt at designe tests - vær sikker på, at algoritmen returnerer de relevante oplysninger for forskellige data, uanset hvordan den fungerer. Sørg for de rigtige grænseflader - så både input og output fra metoderne er læsbare, tydelige og afspejler præcis, hvad de gør.

Jeg nævnte tidligere, at jeg vil vende tilbage til pålideligheden af at måle hastigheden af algoritmerne i eksemplerne ovenfor. At måle dem med console.time er ikke særlig pålideligt, men det afspejler bedst standardtilfældet. Under alle omstændigheder præsenterer jeg benchmarks nedenfor - nogle af dem ser lidt anderledes ud end en enkelt udførelse, fordi benchmarks simpelthen gentager en given aktivitet på et bestemt tidspunkt og bruger v8-optimering til løkker.

  • https://jsben.ch/KhAqb - reduce vs for loop
  • https://jsben.ch/F4kLY - optimeret reduce vs for loop
  • https://jsben.ch/MCr6g - Math.max vs for loop
  • https://jsben.ch/A0CJB - rekursiv fibo vs iterativ fibo
  • https://jsben.ch/NFLsl - hale rekursiv fibo vs iterativ fibo 

Og det var det - god fornøjelse med din hacking!

Læs mere om det:

Hvordan forbedrer man Vue.js-apps? Nogle praktiske tips

Måder at øge din Rails-performance på

Hvordan skriver man en god kvalitetskode?

Relaterede artikler

Udvikling af software

Byg fremtidssikrede webapps: Indsigt fra The Codest's ekspertteam

Oplev, hvordan The Codest udmærker sig ved at skabe skalerbare, interaktive webapplikationer med banebrydende teknologier, der leverer sømløse brugeroplevelser på tværs af alle platforme. Lær, hvordan vores ekspertise driver digital transformation og...

DENKODEST
Udvikling af software

Top 10 Letlands-baserede softwareudviklingsvirksomheder

Læs om Letlands bedste softwareudviklingsvirksomheder og deres innovative løsninger i vores seneste artikel. Find ud af, hvordan disse teknologiledere kan hjælpe med at løfte din virksomhed.

thecodest
Løsninger til virksomheder og scaleups

Grundlæggende om Java-softwareudvikling: En guide til succesfuld outsourcing

Udforsk denne vigtige guide til vellykket outsourcing af Java-softwareudvikling for at forbedre effektiviteten, få adgang til ekspertise og skabe projektsucces med The Codest.

thecodest
Udvikling af software

Den ultimative guide til outsourcing i Polen

Den voldsomme stigning i outsourcing i Polen er drevet af økonomiske, uddannelsesmæssige og teknologiske fremskridt, der fremmer it-vækst og et erhvervsvenligt klima.

TheCodest
Løsninger til virksomheder og scaleups

Den komplette guide til IT-revisionsværktøjer og -teknikker

IT-revisioner sikrer sikre, effektive og kompatible systemer. Lær mere om deres betydning ved at læse hele artiklen.

Codest
Jakub Jakubowicz CTO og medstifter

Tilmeld dig vores vidensbase, og hold dig opdateret om ekspertisen fra it-sektoren.

    Om os

    The Codest - International softwareudviklingsvirksomhed med tech-hubs i Polen.

    Storbritannien - Hovedkvarter

    • Kontor 303B, 182-184 High Street North E6 2JA
      London, England

    Polen - Lokale teknologiske knudepunkter

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Hjerneambassaden, Konstruktorska
      11, 02-673 Warszawa, Polen

      Codest

    • Hjem
    • Om os
    • Serviceydelser
    • Casestudier
    • Ved hvordan
    • Karriere
    • Ordbog

      Serviceydelser

    • Det rådgivende
    • Udvikling af software
    • Backend-udvikling
    • Frontend-udvikling
    • Staff Augmentation
    • Backend-udviklere
    • Cloud-ingeniører
    • Dataingeniører
    • Andet
    • QA-ingeniører

      Ressourcer

    • Fakta og myter om at samarbejde med en ekstern softwareudviklingspartner
    • Fra USA til Europa: Hvorfor beslutter amerikanske startups sig for at flytte til Europa?
    • Sammenligning af Tech Offshore-udviklingsknudepunkter: Tech Offshore Europa (Polen), ASEAN (Filippinerne), Eurasien (Tyrkiet)
    • Hvad er de største udfordringer for CTO'er og CIO'er?
    • Codest
    • Codest
    • Codest
    • Privacy policy
    • Vilkår for brug af hjemmesiden

    Copyright © 2025 af The Codest. Alle rettigheder forbeholdes.

    da_DKDanish
    en_USEnglish de_DEGerman sv_SESwedish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek da_DKDanish