window.pipedriveLeadboosterConfig = { base: pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster on jo olemassa') } 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 }) }, } } })() Muutama temppu JavaScript-sovelluksen nopeuttamiseksi - The Codest
Codest
  • Tietoa meistä
  • Palvelut
    • Ohjelmistokehitys
      • Frontend-kehitys
      • Backend-kehitys
    • Staff Augmentation
      • Frontend-kehittäjät
      • Backend-kehittäjät
      • Tietoinsinöörit
      • Pilvi-insinöörit
      • QA insinöörit
      • Muut
    • Se neuvoa-antava
      • Tilintarkastus & konsultointi
  • Toimialat
    • Fintech & pankkitoiminta
    • E-commerce
    • Adtech
    • Terveysteknologia
    • Valmistus
    • Logistiikka
    • Autoteollisuus
    • IOT
  • Arvo
    • TOIMITUSJOHTAJA
    • CTO
    • Toimituspäällikkö
  • Tiimimme
  • Tapaustutkimukset
  • Tiedä miten
    • Blogi
    • Tapaamiset
    • Webinaarit
    • Resurssit
Työurat Ota yhteyttä
  • Tietoa meistä
  • Palvelut
    • Ohjelmistokehitys
      • Frontend-kehitys
      • Backend-kehitys
    • Staff Augmentation
      • Frontend-kehittäjät
      • Backend-kehittäjät
      • Tietoinsinöörit
      • Pilvi-insinöörit
      • QA insinöörit
      • Muut
    • Se neuvoa-antava
      • Tilintarkastus & konsultointi
  • Arvo
    • TOIMITUSJOHTAJA
    • CTO
    • Toimituspäällikkö
  • Tiimimme
  • Tapaustutkimukset
  • Tiedä miten
    • Blogi
    • Tapaamiset
    • Webinaarit
    • Resurssit
Työurat Ota yhteyttä
Takaisin nuoli PALAA TAAKSE
2020-11-25
Ohjelmistokehitys

Muutamia niksejä JavaScript-sovelluksen nopeuttamiseksi

Codest

Bartosz Slysz

Software Engineer

Selainteknologian kehittyessä verkkosovellukset ovat alkaneet siirtää yhä enemmän logiikkaa etusivulle, mikä vapauttaa palvelinta ja vähentää sen suorittamien operaatioiden määrää. Perus-CRUD-toiminnoissa palvelimen rooli rajoittuu valtuutukseen, validointiin, tietokantojen kanssa kommunikointiin ja tarvittavaan liiketoimintalogiikkaan. Loput datalogiikasta, kuten käy ilmi, voidaan helposti hoitaa koodilla, joka vastaa sovelluksen esittämisestä käyttöliittymän puolella.

Tässä artikkelissa yritän näyttää teille muutamia esimerkkejä ja malleja, jotka auttavat pitämään meidän koodi tehokas, siisti ja nopea.

Ennen kuin menemme syvemmälle konkreettisiin esimerkkeihin - tässä artikkelissa haluan keskittyä vain sellaisten tapausten esittelyyn, jotka mielestäni voivat vaikuttaa sovelluksen nopeuteen yllättävällä tavalla. Tämä ei kuitenkaan tarkoita, että nopeampien ratkaisujen käyttö olisi paras valinta kaikissa mahdollisissa tapauksissa. Alla olevia vinkkejä tulisi pikemminkin pitää sellaisina asioina, joita kannattaa tarkastella silloin, kun sovelluksemme toimii hitaasti, esimerkiksi tuotteissa, jotka vaativat pelien renderöintiä tai edistyneempiä kuvaajia kankaalla, video-operaatioita tai toimintoja, jotka halutaan synkronoida reaaliajassa mahdollisimman pian.

Ensinnäkin - Array.prototype-metodit

Suuri osa sovelluslogiikasta perustuu matriiseihin - niiden yhdistämiseen, lajitteluun, suodatukseen, elementtien yhteenlaskuun ja niin edelleen. Käytämme helposti, läpinäkyvästi ja luonnollisesti niiden sisäänrakennettuja metodeja, joiden avulla voimme yksinkertaisesti suorittaa erilaisia laskutoimituksia, ryhmittelyjä jne. Ne toimivat jokaisessa tapauksessa samalla tavalla - argumenttina välitämme funktion, jossa useimmissa tapauksissa elementin arvo, indeksi ja array työnnetään vuorotellen jokaisen iteraation aikana. Määritelty funktio suoritetaan jokaiselle matriisin elementille, ja tulos tulkitaan eri tavalla menetelmästä riippuen. En käsittele tarkemmin Array.prototype-metodeja, koska haluan keskittyä siihen, miksi se toimii hitaasti suuressa osassa tapauksia.

Array-metodit ovat hitaita, koska ne suorittavat toiminnon jokaiselle elementille. Moottorin näkökulmasta kutsutun funktion on valmisteltava uusi kutsu, annettava sopiva laajuus ja paljon muita riippuvuuksia, mikä tekee prosessista paljon pidemmän kuin tietyn koodilohkon toistaminen tietyssä laajuudessa. Ja tämä on luultavasti tarpeeksi taustatietoa, jotta voimme ymmärtää seuraavan esimerkin:

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

console.time('Summa vähentämällä');
const reduceSum = randomArray
    .map(({ value }) => value)
    .reduce((a, b) => a + b);
console.timeEnd('Sum by reduce');

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

console.log(reduceSum === forSum);

})();

Tiedän, että tämä testi ei ole yhtä luotettava kuin vertailuarvot (palaamme niihin myöhemmin), mutta se käynnistää varoitusvalon. Satunnaisessa tapauksessa tietokoneellani käy ilmi, että for-silmukan sisältävä koodi voi olla noin 50 kertaa nopeampi, jos sitä verrataan elementtien kartoittamiseen ja sen jälkeen vähentämiseen, joilla saavutetaan sama vaikutus! Kyse on jonkin oudon objektin käyttämisestä, joka on luotu vain tietyn laskentakohteen saavuttamiseksi. Luodaan siis jotain legitiimimpää, jotta Array-menetelmät olisivat objektiivisia:

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

console.time('Summa vähentämällä');
const reduceSum = randomArray
    .reduce((a, b) => ({ arvo: a.value + b.value })).value
console.timeEnd('Sum by reduce');

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

console.log(reduceSum === forSum);

})();

Tiedän, että tämä testi ei ole yhtä luotettava kuin vertailuarvot (palaamme niihin myöhemmin), mutta se käynnistää varoitusvalon. Satunnaisessa tapauksessa tietokoneellani käy ilmi, että for-silmukan sisältävä koodi voi olla noin 50 kertaa nopeampi, jos sitä verrataan elementtien kartoittamiseen ja sen jälkeen vähentämiseen, joilla saavutetaan sama vaikutus! Tämä johtuu siitä, että summan saaminen tässä nimenomaisessa tapauksessa reduce-menetelmää käyttäen edellyttää arrayn kartoittamista puhtaiden arvojen osalta, jotka haluamme tiivistää. Luodaan siis jotain legitiimimpää, jotta voidaan olla objektiivisia Array-metodeja kohtaan:

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

console.time('Summa vähentämällä');
const reduceSum = randomArray
    .reduce((a, b) => ({ arvo: a.value + b.value })).value
console.timeEnd('Sum by reduce');

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

console.log(reduceSum === forSum);

})();

Ja kuten kävi ilmi, 50-kertainen boostimme laski 4x boostiksi. Pahoittelut, jos olet pettynyt! Jotta pysyisimme objektiivisina loppuun asti, analysoidaan molemmat koodit uudelleen. Ensinnäkin - viattoman näköiset erot kaksinkertaistivat teoreettisen laskennallisen monimutkaisuutemme laskun; sen sijaan, että ensin kartoittaisimme ja sitten laskisimme yhteen puhtaita elementtejä, toimimme edelleen objekteilla ja tietyllä kentällä, jotta voimme lopulta vetää ulos saadaksemme summan, josta olemme kiinnostuneita. Ongelma syntyy, kun toinen ohjelmoija vilkaisee koodia - silloin, verrattuna aiemmin esitettyihin koodeihin, jälkimmäinen menettää jossain vaiheessa abstraktionsa.

Tämä johtuu siitä, että koska toinen operaatio, että toimimme outo objekti, jossa kenttä kiinnostaa meitä ja toinen, standardi objekti iteroitu array. En tiedä, mitä mieltä olet siitä, mutta minun näkökulmastani toisessa koodiesimerkissä for-silmukan logiikka on paljon selkeämpi ja tarkoituksenmukaisempi kuin tämä oudon näköinen redusointi. Ja silti, vaikka se ei enää olekaan se myyttinen 50, se on silti neljä kertaa nopeampi, kun on kyse laskentaajasta! Koska jokainen millisekunti on arvokas, valinta on tässä tapauksessa yksinkertainen.

Yllättävin esimerkki

Toinen asia, jota halusin verrata, koskee Math.max-menetelmää tai tarkemmin sanottuna miljoonan elementin täyttämistä ja suurimpien ja pienimpien elementtien poimimista. Valmistelin koodin, menetelmiä myös ajan mittaamista varten, sitten käynnistän koodin ja saan hyvin oudon virheen - pinon koko on ylitetty. Tässä on koodi:

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

console.time('Math.max ES6:n leviämisoperaattorilla');
const maxBySpread = Math.max(...randomValues);
console.timeEnd('Math.max with ES6 spread operator');

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

console.log(maxByFor === maxBySpread);

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

console.time('Math.max ES6:n leviämisoperaattorilla');
const maxBySpread = Math.max(...randomValues);
console.timeEnd('Math.max with ES6 spread operator');

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

console.log(maxByFor === maxBySpread);

})();

On käynyt ilmi, että natiivit metodit käyttävät rekursiota, jota v8:ssa rajoittavat kutsupinot, ja sen määrä riippuu ympäristöstä. Tämä yllätti minut kovasti, mutta siitä voidaan vetää johtopäätös: natiivimenetelmä on nopeampi, kunhan joukkomme ei ylitä tiettyä maagista alkioiden lukumäärää, joka minun tapauksessani osoittautui 125375:ksi. Tällä elementtien määrällä tulos for oli 5x nopeampi, jos sitä verrataan silmukkaan. Mainitun elementtimäärän yläpuolella for-silmukka kuitenkin voittaa ehdottomasti - toisin kuin vastustaja, sen avulla saamme oikeat tulokset.

Rekursio

Käsite, jonka haluan mainita tässä kohdassa, on rekursio. Edellisessä esimerkissä näimme sen Math.max-metodissa ja argumenttien taittamisessa, jossa kävi ilmi, että tietyn luvun ylittävistä rekursiivisista kutsuista ei voi saada tulosta pinon kokorajoituksen vuoksi.

Katsomme nyt, miltä rekursio näyttää JS-kielellä kirjoitetun koodin yhteydessä, eikä sisäänrakennettujen metodien avulla.Ehkä klassisin asia, jonka voimme näyttää tässä, on tietenkin Fibonaccin sarjan n:nnen termin löytäminen. Kirjoitetaan siis tämä!

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

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

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

console.time('Fibonacci-sekvenssi for-silmukalla');
const resultIterative = fiboIterative(30);
console.timeEnd('Fibonaccin sarja for-silmukalla');

console.time('Fibonaccin sarja rekursiolla');
const resultRecursive = fiboRecursive(30);
console.timeEnd('Fibonaccin sarja rekursiolla');

console.log(resultRecursive === resultIterative);

})();

Okei, tässä erityistapauksessa, kun laskemme sarjan 30. kohdan tietokoneellani, saamme tuloksen noin 200 kertaa lyhyemmässä ajassa iteratiivisella algoritmilla.

Rekursiivisessa algoritmissa on kuitenkin yksi asia, joka voidaan korjata - kuten käy ilmi, se toimii paljon tehokkaammin, kun käytämme taktiikkaa nimeltä häntärekursio. Tämä tarkoittaa sitä, että välitämme edellisessä iteraatiossa saamamme tuloksen käyttäen argumentteja syvempiin kutsuihin. Näin voimme vähentää tarvittavien kutsujen määrää ja nopeuttaa tuloksen saamista. Korjataan koodimme sen mukaisesti!

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

    for (let i = 0; i  {
    if(n === 0) {
        return first;
    }

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

console.time('Fibonacci-sekvenssi for-silmukalla');
const resultIterative = fiboIterative(30);
console.timeEnd('Fibonacci sequence by for loop');

console.time('Fibonaccisekvenssi häntärekursiolla');
const resultRecursive = fiboTailRecursive(30);
console.timeEnd('Fibonaccin sarja häntärekursion avulla');

console.log(resultRecursive === resultIterative);

})();

Tässä tapahtui jotakin, mitä en odottanut - häntärekursio-algoritmin tulos pystyi joissakin tapauksissa toimittamaan tuloksen (sarjan 30. elementin laskeminen) lähes kaksi kertaa nopeammin kuin iteratiivinen algoritmi. En ole täysin varma, johtuuko tämä v8:n optimoinnista häntärekursion osalta vai siitä, että for-silmukkaa ei optimoitu tätä tiettyä iteraatiomäärää varten, mutta tulos on yksiselitteinen - häntärekurssi voittaa.

Tämä on outoa, koska for-silmukka on paljon vähemmän abstraktio alemman tason laskutoimituksille, ja voisi sanoa, että se on lähempänä tietokoneen perustoimintoja. Silti tulokset ovat kiistattomia - taitavasti suunniteltu rekursio osoittautuu nopeammaksi kuin iterointi.

Käytä asynkronisia kutsulausekkeita niin usein kuin mahdollista.

Haluaisin omistaa viimeisen kappaleen lyhyelle muistutukselle eräästä toimintatavasta, joka voi myös vaikuttaa suuresti sovelluksemme nopeuteen. Kuten sinun pitäisi tietää, JavaScript on yksisäikeinen kieli, joka pitää kaikki operaatiot tapahtumasilmukkamekanismilla. Kyse on syklistä, joka kulkee yhä uudelleen ja uudelleen, ja kaikki tämän syklin vaiheet koskevat erityisiä määritettyjä toimintoja.

Jotta tämä silmukka olisi nopea ja jotta kaikki syklit voisivat odottaa vuoroaan vähemmän, kaikkien elementtien tulisi olla mahdollisimman nopeita. Vältä pitkien operaatioiden suorittamista pääsäikeessä - jos jokin laskutoimitus kestää liian kauan, yritä siirtää nämä laskutoimitukset WebWorkeriin tai jakaa ne osiin, jotka suoritetaan asynkronisesti. Se saattaa hidastaa joitakin toimintoja, mutta tehostaa koko JS:n ekosysteemiä, myös IO-operaatioita, kuten hiiren liikkeiden käsittelyä tai odottavaa HTTP-pyyntöä.

Yhteenveto

Kuten aiemmin mainittiin, algoritmin valinnalla säästettävien millisekuntien jahtaaminen voi joissakin tapauksissa osoittautua järjettömäksi. Toisaalta tällaisten asioiden laiminlyöminen sovelluksissa, jotka vaativat sujuvaa toimintaa ja nopeita tuloksia, voi olla sovelluksen kannalta tappavaa. Joissain tapauksissa algoritmin nopeuden lisäksi on kysyttävä vielä yksi kysymys: onko abstraktio oikealla tasolla? Pystyykö koodia lukeva ohjelmoija ymmärtämään sen ongelmitta?

Ainoa tapa on varmistaa tasapaino suorituskyvyn, toteutuksen helppouden ja asianmukaisen abstraktion välillä ja olla varma siitä, että algoritmi toimii oikein sekä pienillä että suurilla tietomäärillä. Tapa tehdä tämä on melko yksinkertainen - ole fiksu, ota eri tapaukset huomioon algoritmia suunnitellessasi ja järjestä se niin, että se käyttäytyy mahdollisimman tehokkaasti keskimääräisissä suorituksissa. Lisäksi on suositeltavaa suunnitella testejä - varmista, että algoritmi palauttaa sopivaa tietoa eri datan osalta, riippumatta siitä, miten se toimii. Huolehdi oikeista rajapinnoista - jotta sekä metodien tulo että lähtö ovat luettavia, selkeitä ja kuvaavat täsmälleen sitä, mitä ne tekevät.

Mainitsin aiemmin, että palaan vielä edellä mainituissa esimerkeissä algoritmien nopeuden mittaamisen luotettavuuteen. Niiden mittaaminen console.time-arvolla ei ole kovin luotettavaa, mutta se kuvastaa parhaiten tavanomaista käyttötapausta. Joka tapauksessa esittelen alla olevat vertailuarvot - jotkut niistä näyttävät hieman erilaisilta kuin yksittäinen suoritus, koska vertailuarvot yksinkertaisesti toistavat tietyn toiminnon tiettynä aikana ja käyttävät silmukoille v8-optimointia.

  • https://jsben.ch/KhAqb - vähentäminen vs for-silmukka
  • https://jsben.ch/F4kLY - optimoitu vähentäminen vs for-silmukka
  • https://jsben.ch/MCr6g - Math.max vs for loop
  • https://jsben.ch/A0CJB - rekursiivinen fibo vs. iteratiivinen fibo
  • https://jsben.ch/NFLsl - hännän rekursiivinen fibo vs iteratiivinen fibo 

Siinä kaikki - nauti hakkeroinnista!

Lue lisää:

Miten parantaa Vue.js-sovelluksia? Joitakin käytännön vinkkejä

Tapoja lisätä Railsin suorituskykyä

Miten kirjoittaa hyvää ja laadukasta koodia?

Aiheeseen liittyvät artikkelit

Ohjelmistokehitys

Tulevaisuuden web-sovellusten rakentaminen: The Codest:n asiantuntijatiimin näkemyksiä

Tutustu siihen, miten The Codest loistaa skaalautuvien, interaktiivisten verkkosovellusten luomisessa huipputeknologian avulla ja tarjoaa saumattomia käyttäjäkokemuksia kaikilla alustoilla. Lue, miten asiantuntemuksemme edistää digitaalista muutosta ja liiketoimintaa...

THECODEST
Ohjelmistokehitys

Top 10 Latviassa toimivaa ohjelmistokehitysyritystä

Tutustu Latvian parhaisiin ohjelmistokehitysyrityksiin ja niiden innovatiivisiin ratkaisuihin uusimmassa artikkelissamme. Tutustu siihen, miten nämä teknologiajohtajat voivat auttaa nostamaan liiketoimintaasi.

thecodest
Yritys- ja skaalausratkaisut

Java-ohjelmistokehityksen perusteet: A Guide to Outsourcing Successfully

Tutustu tähän keskeiseen oppaaseen Java-ohjelmistokehityksen onnistuneesta ulkoistamisesta tehokkuuden parantamiseksi, asiantuntemuksen saamiseksi ja projektin onnistumiseksi The Codestin avulla.

thecodest
Ohjelmistokehitys

Perimmäinen opas ulkoistamiseen Puolassa

Ulkoistamisen lisääntyminen Puolassa johtuu taloudellisesta, koulutuksellisesta ja teknologisesta kehityksestä, joka edistää tietotekniikan kasvua ja yritysystävällistä ilmapiiriä.

TheCodest
Yritys- ja skaalausratkaisut

Täydellinen opas IT-tarkastustyökaluihin ja -tekniikoihin

Tietotekniikan tarkastuksilla varmistetaan turvalliset, tehokkaat ja vaatimustenmukaiset järjestelmät. Lue lisää niiden merkityksestä lukemalla koko artikkeli.

Codest
Jakub Jakubowicz teknologiajohtaja ja toinen perustaja

Tilaa tietopankkimme ja pysy ajan tasalla IT-alan asiantuntemuksesta.

    Tietoa meistä

    The Codest - Kansainvälinen ohjelmistokehitysyritys, jolla on teknologiakeskuksia Puolassa.

    Yhdistynyt kuningaskunta - pääkonttori

    • Toimisto 303B, 182-184 High Street North E6 2JA
      Lontoo, Englanti

    Puola - Paikalliset teknologiakeskukset

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Krakova
    • Brain Embassy, Konstruktorska
      11, 02-673 Varsova, Puola

      Codest

    • Etusivu
    • Tietoa meistä
    • Palvelut
    • Tapaustutkimukset
    • Tiedä miten
    • Työurat
    • Sanakirja

      Palvelut

    • Se neuvoa-antava
    • Ohjelmistokehitys
    • Backend-kehitys
    • Frontend-kehitys
    • Staff Augmentation
    • Backend-kehittäjät
    • Pilvi-insinöörit
    • Tietoinsinöörit
    • Muut
    • QA insinöörit

      Resurssit

    • Faktoja ja myyttejä yhteistyöstä ulkoisen ohjelmistokehityskumppanin kanssa
    • Yhdysvalloista Eurooppaan: Miksi amerikkalaiset startup-yritykset päättävät muuttaa Eurooppaan?
    • Tech Offshore -kehityskeskusten vertailu: Tech Offshore Eurooppa (Puola), ASEAN (Filippiinit), Euraasia (Turkki).
    • Mitkä ovat teknologiajohtajien ja tietohallintojohtajien tärkeimmät haasteet?
    • Codest
    • Codest
    • Codest
    • Privacy policy
    • Verkkosivuston käyttöehdot

    Tekijänoikeus © 2025 by The Codest. Kaikki oikeudet pidätetään.

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