The Codest
  • O nás
  • Služby
    • Vývoj softwaru
      • Vývoj frontendů
      • Vývoj backendu
    • Staff Augmentation
      • Vývojáři frontendů
      • Vývojáři backendu
      • Datoví inženýři
      • Cloudoví inženýři
      • Inženýři QA
      • Další
    • To Advisory
      • Audit a poradenství
  • Odvětví
    • Fintech a bankovnictví
    • E-commerce
    • Adtech
    • Healthtech
    • Výroba
    • Logistika
    • Automobilový průmysl
    • IOT
  • Hodnota za
    • CEO
    • CTO
    • Manažer dodávek
  • Náš tým
  • Case Studies
  • Vědět jak
    • Blog
    • Setkání
    • Webové semináře
    • Zdroje
Kariéra Spojte se s námi
  • O nás
  • Služby
    • Vývoj softwaru
      • Vývoj frontendů
      • Vývoj backendu
    • Staff Augmentation
      • Vývojáři frontendů
      • Vývojáři backendu
      • Datoví inženýři
      • Cloudoví inženýři
      • Inženýři QA
      • Další
    • To Advisory
      • Audit a poradenství
  • Hodnota za
    • CEO
    • CTO
    • Manažer dodávek
  • Náš tým
  • Case Studies
  • Vědět jak
    • Blog
    • Setkání
    • Webové semináře
    • Zdroje
Kariéra Spojte se s námi
Šipka zpět ZPĚT
2020-11-25
Vývoj softwaru

Několik triků pro zrychlení aplikace JavaScript

The Codest

Bartosz Slysz

Software Engineer

S rozvojem technologie prohlížečů začaly webové aplikace přenášet stále více logiky na front-end, čímž se odlehčilo serveru a snížil se počet operací, které musí provádět. V základních CRUD se úloha serveru omezuje na autorizaci, validaci, komunikaci s databázemi a potřebnou obchodní logiku. Zbytek datové logiky, jak se ukazuje, může snadno zvládnout kód zodpovědný za reprezentaci aplikace na straně uživatelského rozhraní.

V tomto článku se vám pokusím ukázat několik příkladů a vzorů, které nám pomohou udržet naše kód efektivní, úhledné a rychlé.

Než se pustíme do konkrétních příkladů - v tomto článku bych se chtěl zaměřit pouze na ukázku případů, které podle mého názoru mohou překvapivě ovlivnit rychlost aplikace. To však neznamená, že použití rychlejších řešení je nejlepší volbou ve všech možných případech. Níže uvedené tipy bychom měli brát spíše jako něco, čím bychom se měli zabývat v případě, že naše aplikace běží pomalu, např. v produktech, které vyžadují vykreslování her nebo pokročilejších grafů na plátně, operace s videem nebo činnosti, které chceme synchronizovat v reálném čase co nejdříve.

Především - metody Array.prototype

Velkou část aplikační logiky zakládáme na polích - jejich mapování, třídění, filtrování, sčítání prvků atd. Snadno, přehledně a přirozeně využíváme jejich vestavěné metody, které jednoduše umožňují nás k provádění různých typů výpočtů, seskupování atd. V každém případě fungují podobně - jako argument předáváme funkci, kde se ve většině případů při každé iteraci střídavě posouvá hodnota prvku, index a pole. Zadaná funkce se provede pro každý prvek pole a výsledek se interpretuje různě v závislosti na metodě. Nebudu se podrobněji zabývat metodami Array.prototype, protože se chci zaměřit na to, proč ve velkém množství případů běží pomalu.

Metody pole jsou pomalé, protože provádějí funkci pro každý prvek. Funkce volaná z pohledu enginu musí připravit nové volání, poskytnout příslušný obor a spoustu dalších závislostí, což celý proces značně prodlužuje oproti opakování konkrétního bloku kódu v konkrétním oboru. A to jsou pravděpodobně dostatečné základní znalosti, které nám umožní pochopit následující příklad:

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

console.time('Sum by reduce');
const reduceSum = randomArray
    .map(({ value }) => value)
    .reduce((a, b) => a + b);
console.timeEnd('Součet podle reduce');

console.time('Součet podle smyčky for');
let forSum = randomArray[0].value;
for (let index = 1; index < randomArray.length; index++) {
    forSum += randomArray[index].value;
}
console.timeEnd('Součet podle cyklu for');

console.log(reduceSum === forSum);

})();

Vím, že tento test není tak spolehlivý jako benchmarky (k nim se vrátíme později), ale spustí varovnou kontrolku. Pro náhodný případ na mém počítači se ukázalo, že kód se smyčkou for může být asi 50krát rychlejší, pokud ho porovnáme s mapováním a následným zmenšováním prvků, které dosáhnou stejného efektu! Jde o práci s nějakým podivným objektem vytvořeným pouze za účelem dosažení určitého cíle výpočtů. Vytvořme si tedy něco legitimnějšího, abychom byli objektivní ohledně metod Array:

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

console.time('Sum by reduce');
const reduceSum = randomArray
    .reduce((a, b) => ({ hodnota: a.value + b.value })).value
console.timeEnd('Součet podle reduce');

console.time('Součet podle smyčky for');
let forSum = randomArray[0].value;
for (let index = 1; index < randomArray.length; index++) {
    forSum += randomArray[index].value;
}
console.timeEnd('Součet podle cyklu for');

console.log(reduceSum === forSum);

})();

Vím, že tento test není tak spolehlivý jako benchmarky (k nim se vrátíme později), ale spustí varovnou kontrolku. Pro náhodný případ na mém počítači se ukázalo, že kód se smyčkou for může být asi 50krát rychlejší, pokud ho porovnáme s mapováním a následným zmenšováním prvků, které dosáhnou stejného efektu! Je to proto, že získání součtu v tomto konkrétním případě pomocí metody reduce vyžaduje mapování pole pro čisté hodnoty, které chceme sečíst. Vytvořme si tedy něco legitimnějšího, abychom byli objektivní ohledně metod pole:

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

console.time('Sum by reduce');
const reduceSum = randomArray
    .reduce((a, b) => ({ hodnota: a.value + b.value })).value
console.timeEnd('Součet podle reduce');

console.time('Součet podle smyčky for');
let forSum = randomArray[0].value;
for (let index = 1; index < randomArray.length; index++) {
    forSum += randomArray[index].value;
}
console.timeEnd('Součet podle cyklu for');

console.log(reduceSum === forSum);

})();

A jak se ukázalo, naše 50násobné zvýšení se snížilo na 4násobné. Omlouváme se, pokud se cítíte zklamaní! Abychom zůstali objektivní až do konce, analyzujme oba kódy ještě jednou. Především - nevinně vypadající rozdíly zdvojnásobily pokles naší teoretické výpočetní složitosti; místo toho, abychom nejprve mapovali a pak sčítali čisté prvky, stále operujeme s objekty a konkrétním polem, abychom nakonec vytáhli a získali součet, který nás zajímá. Problém nastane, když se na kód podívá jiný programátor - ten pak ve srovnání s dříve uvedenými kódy v určitém bodě ztrácí abstrakci.

Je to proto, že od druhé operace, kterou pracujeme s cizím objektem, s polem, které nás zajímá, a druhým, standardním objektem iterovaného pole. Nevím, co si o tom myslíte vy, ale z mého pohledu je v druhém příkladu kódu logika cyklu for mnohem jasnější a záměrnější než tato podivně vypadající redukce. A přesto, i když už to není bájná padesátka, je to stále 4krát rychlejší, pokud jde o výpočetní čas! Protože každá milisekunda je cenná, je volba v tomto případě jednoduchá.

Nejpřekvapivější příklad

Druhá věc, kterou jsem chtěl porovnat, se týká metody Math.max, přesněji řečeno, vycpání milionu prvků a jejich extrakce těch největších a nejmenších. Připravil jsem si kód, metody pro měření času také, pak kód spustím a dostanu velmi podivnou chybu - je překročena velikost zásobníku. Zde je kód:

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

console.time('Math.max s operátorem ES6 spread');
const maxBySpread = Math.max(...randomValues);
console.timeEnd('Math.max s operátorem ES6 spread');

console.time('Math.max s for cyklem');
let maxByFor = randomValues[0];
for (let index = 1; index  maxByFor) {
        maxByFor = randomValues[index];
    }
}
console.timeEnd('Math.max se smyčkou for');

console.log(maxByFor === maxBySpread);

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

console.time('Math.max s operátorem ES6 spread');
const maxBySpread = Math.max(...randomValues);
console.timeEnd('Math.max s operátorem ES6 spread');

console.time('Math.max s for cyklem');
let maxByFor = randomValues[0];
for (let index = 1; index  maxByFor) {
        maxByFor = randomValues[index];
    }
}
console.timeEnd('Math.max se smyčkou for');

console.log(maxByFor === maxBySpread);

})();

Ukázalo se, že nativní metody používají rekurzi, která je ve verzi v8 omezena zásobníky volání a její počet závisí na prostředí. To je něco, co mě hodně překvapilo, ale nese to s sebou závěr: nativní metoda je rychlejší, pokud naše pole nepřesáhne určitý magický počet prvků, který se v mém případě ukázal být 125375. Pro tento počet prvků byl výsledek for 5x rychlejší, pokud se porovnával se smyčkou. Nad zmíněným počtem prvků však smyčka for jednoznačně vítězí - na rozdíl od soupeře nám umožňuje získat správné výsledky.

Rekurze

Koncept, který chci v tomto odstavci zmínit, je rekurze. V předchozím příkladu jsme se s ní setkali u metody Math.max a skládání argumentů, kde se ukázalo, že u rekurzivních volání přesahujících určité číslo nelze získat výsledek kvůli omezení velikosti zásobníku.

Nyní se podíváme, jak vypadá rekurze v kontextu kódu napsaného v JS, a ne pomocí vestavěných metod.Asi nejklasičtější věcí, kterou si zde můžeme ukázat, je samozřejmě nalezení n-tého členu Fibonacciho posloupnosti. Tak si to pojďme napsat!

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

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

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

console.time('Fibonacciho posloupnost pomocí smyčky for');
const resultIterative = fiboIterative(30);
console.timeEnd('Fibonacciho posloupnost podle smyčky for');

console.time('Fibonacciho posloupnost rekurzí');
const resultRecursive = fiboRecursive(30);
console.timeEnd('Fibonacciho posloupnost rekurzí');

console.log(resultRecursive === resultIterative);

})();

Dobře, v tomto konkrétním případě výpočtu 30. položky posloupnosti na mém počítači získáme výsledek iteračním algoritmem asi za 200x kratší dobu.

V rekurzivním algoritmu však lze napravit jednu věc - jak se ukázalo, funguje mnohem efektivněji, když použijeme taktiku zvanou ocasní rekurze. To znamená, že výsledek, který jsme získali v předchozí iteraci, předáme pomocí argumentů pro hlubší volání. To nám umožňuje snížit počet potřebných volání a v důsledku toho zrychlit výsledek. Opravme tedy náš kód odpovídajícím způsobem!

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

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

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

console.time('Fibonacciho posloupnost podle smyčky for');
const resultIterative = fiboIterative(30);
console.timeEnd('Fibonacciho posloupnost podle smyčky for');

console.time('Fibonacciho posloupnost pomocí rekurze na chvostu');
const resultRecursive = fiboTailRecursive(30);
console.timeEnd('Fibonacciho posloupnost pomocí rekurze na chvostu');

console.log(resultRecursive === resultIterative);

})();

Stalo se zde něco, co jsem tak docela nečekal - výsledek algoritmu rekurze na chvostu byl v některých případech schopen poskytnout výsledek (výpočet 30. prvku posloupnosti) téměř dvakrát rychleji než iterační algoritmus. Nejsem si zcela jist, zda je to způsobeno optimalizací pro ocasní rekurzi ze strany v8, nebo nedostatkem optimalizace cyklu for pro tento konkrétní počet iterací, ale výsledek je jednoznačný - ocasní rekurze vítězí.

To je zvláštní, protože smyčka for v podstatě ukládá mnohem méně abstrakce na výpočetní činnosti nižší úrovně a dalo by se říci, že má blíže k základním operacím počítače. Přesto jsou výsledky nepopiratelné - chytře navržená rekurze se ukazuje být rychlejší než iterace.

Používejte asynchronní příkazy volání co nejčastěji.

Poslední odstavec bych rád věnoval krátkému připomenutí způsobu provádění operací, který také může výrazně ovlivnit rychlost naší aplikace. Jak byste měli vědět, JavaScript je jednovláknový jazyk, který všechny operace provádí pomocí mechanismu smyčky událostí. Jde o cyklus, který běží stále dokola a všechny kroky v tomto cyklu se týkají vyhrazených zadaných akcí.

Aby byla tato smyčka rychlá a všechny cykly čekaly méně na svůj čas, měly by být všechny prvky co nejrychlejší. Vyhněte se spouštění dlouhých operací na hlavním vlákně - pokud něco trvá příliš dlouho, pokuste se tyto výpočty přesunout do WebWorkeru nebo rozdělit na části, které běží asynchronně. Může to sice zpomalit některé operace, ale podpoří to celý ekosystém JS, včetně IO operací, jako je obsluha pohybu myši nebo čekajícího HTTP požadavku.

Souhrn

Závěrem, jak již bylo zmíněno, honba za milisekundami, které lze ušetřit výběrem algoritmu, se může v některých případech ukázat jako nesmyslná. Na druhou stranu zanedbávání takových věcí v aplikacích, které vyžadují plynulý chod a rychlé výsledky, může být pro vaši aplikaci smrtící. V některých případech je třeba si kromě rychlosti algoritmu položit ještě jednu otázku - je abstrakce provozována na správné úrovni? Bude programátor, který kód čte, schopen mu bez problémů porozumět?

Jediným způsobem je zajistit rovnováhu mezi výkonem, snadnou implementací a vhodnou abstrakcí a mít jistotu, že algoritmus funguje správně jak pro malé, tak pro velké objemy dat. Způsob, jak toho dosáhnout, je poměrně jednoduchý - buďte chytří, při návrhu algoritmu zvažte různé případy a zařiďte jej tak, aby se choval co nejefektivněji pro průměrné provedení. Také je vhodné navrhnout testy - ujistěte se, že algoritmus vrací vhodné informace pro různá data, ať už pracuje jakkoli. Dbejte na správná rozhraní - aby vstupy i výstupy metod byly čitelné, přehledné a přesně odrážely to, co dělají.

Již dříve jsem se zmínil, že se vrátím ke spolehlivosti měření rychlosti algoritmů ve výše uvedených příkladech. Jejich měření pomocí console.time není příliš spolehlivé, ale nejlépe odráží standardní případ použití. Každopádně níže uvádím benchmarky - některé z nich vypadají trochu jinak než jednorázové provedení vzhledem k tomu, že benchmarky prostě opakují danou činnost v určitém čase a používají optimalizaci v8 pro smyčky.

  • https://jsben.ch/KhAqb - redukce vs. smyčka for
  • https://jsben.ch/F4kLY - optimalizovaná redukce vs. smyčka for
  • https://jsben.ch/MCr6g - Math.max vs for smyčka
  • https://jsben.ch/A0CJB - rekurzivní fibo vs iterativní fibo
  • https://jsben.ch/NFLsl - rekurzivní fibo vs iterativní fibo 

A to je vše - užijte si hacking!

Přečtěte si více:

Jak vylepšit aplikace Vue.js? Několik praktických tipů

Způsoby, jak zvýšit výkon systému Rails

Jak napsat dobrý a kvalitní kód?

Související články

Ilustrace zdravotnické aplikace pro chytré telefony s ikonou srdce a rostoucím zdravotním grafem, označená logem The Codest, která představuje digitální zdraví a řešení HealthTech.
Vývoj softwaru

Softwarové vybavení pro zdravotnictví: a případy použití

Nástroje, na které se dnes zdravotnické organizace spoléhají, se v ničem nepodobají papírovým kartám z doby před desítkami let. zdravotnický software dnes podporuje zdravotnické systémy, péči o pacienty a moderní poskytování zdravotní péče v klinických a...

NEJKRÁSNĚJŠÍ
Abstraktní ilustrace klesajícího sloupcového grafu se stoupající šipkou a zlatou mincí symbolizující efektivitu nákladů nebo úspory. V levém horním rohu se zobrazuje logo The Codest se sloganem "In Code We Trust" na světle šedém pozadí.
Vývoj softwaru

Jak rozšířit tým vývojářů bez ztráty kvality produktu

Zvětšujete svůj vývojový tým? Zjistěte, jak růst, aniž byste museli obětovat kvalitu produktu. Tento průvodce se zabývá příznaky, že je čas na škálování, strukturou týmu, najímáním zaměstnanců, vedením a nástroji - a také tím, jak může The Codest...

NEJKRÁSNĚJŠÍ
Vývoj softwaru

Vytváření webových aplikací odolných vůči budoucnosti: postřehy týmu odborníků The Codest

Zjistěte, jak společnost The Codest vyniká při vytváření škálovatelných, interaktivních webových aplikací pomocí nejmodernějších technologií, které poskytují bezproblémové uživatelské prostředí na všech platformách. Zjistěte, jak naše odborné znalosti podporují digitální transformaci a obchodní...

NEJKRÁSNĚJŠÍ
Vývoj softwaru

10 nejlepších lotyšských společností zabývajících se vývojem softwaru

V našem nejnovějším článku se dozvíte o nejlepších lotyšských společnostech zabývajících se vývojem softwaru a jejich inovativních řešeních. Zjistěte, jak mohou tito technologičtí lídři pomoci pozvednout vaše podnikání.

thecodest
Podniková a škálovací řešení

Základy vývoje softwaru v jazyce Java: A Guide to Outsourcing Successfully

Prozkoumejte tuto základní příručku o úspěšném vývoji softwaru outsourcing Java, abyste zvýšili efektivitu, získali přístup k odborným znalostem a dosáhli úspěchu projektu s The Codest.

thecodest

Přihlaste se k odběru naší znalostní databáze a získejte aktuální informace o odborných znalostech z oblasti IT.

    O nás

    The Codest - Mezinárodní společnost zabývající se vývojem softwaru s technologickými centry v Polsku.

    Spojené království - ústředí

    • Kancelář 303B, 182-184 High Street North E6 2JA
      Londýn, Anglie

    Polsko - Místní technologická centra

    • Kancelářský park Fabryczna, Aleja
      Pokoju 18, 31-564 Krakov
    • Brain Embassy, Konstruktorska
      11, 02-673 Varšava, Polsko

      The Codest

    • Home
    • O nás
    • Služby
    • Case Studies
    • Vědět jak
    • Kariéra
    • Slovník

      Služby

    • To Advisory
    • Vývoj softwaru
    • Vývoj backendu
    • Vývoj frontendů
    • Staff Augmentation
    • Vývojáři backendu
    • Cloudoví inženýři
    • Datoví inženýři
    • Další
    • Inženýři QA

      Zdroje

    • Fakta a mýty o spolupráci s externím partnerem pro vývoj softwaru
    • Z USA do Evropy: Proč se americké startupy rozhodly přesídlit do Evropy?
    • Srovnání technických vývojových center v zahraničí: Tech Offshore Evropa (Polsko), ASEAN (Filipíny), Eurasie (Turecko)
    • Jaké jsou hlavní výzvy CTO a CIO?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Copyright © 2026 by The Codest. Všechna práva vyhrazena.

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