Nasza strona używa cookies. Dowiedz się więcej o celu ich używania i zmianie ustawień w przeglądarce. Korzystając ze strony, wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki. Rozumiem

Wydajność kontra czytelność w JavaScript

Riccardo Polacci Frontend Developer / Gaming Innovation Group
Dowiedz się, kiedy warto postawić na wydajność, a kiedy na czytelność kodu i czy da się pogodzić te dwa podejścia w JavaScript oraz kiedy zdecydować się na użycie ES6.
Wydajność kontra czytelność w JavaScript

JavaScript ewoluuje w coraz to bardziej czytelny język. Nie ma co do tego wątpliwości i ani powodu do żadnych obaw. Rozwój oprogramowania to dynamiczny rynek, na którym zespoły nieustannie się zmieniają, co oznacza, że ​​kod musi być czytelny dla nowych użytkowników. Ale czy dzieje się to kosztem wydajności? Gdzie wyznaczyć granicę między wydajnością a czytelnością? Kiedy powinniśmy poświęcić jedno czy drugie? Czy w ogóle musimy coś poświęcać?

Oto niektóre z pytań, na które chciałbym dziś odpowiedzieć lub przynajmniej spróbować wspólnie je zrozumieć. Powody, dla których staramy się osiągnąć wysoki poziom wydajności w kodzie, powinny być oczywiste. Ale dlaczego mamy taką obsesję na punkcie czytelności?


Ten sam problem, różne rozwiązania

Cóż, jakiś czas temu bardzo często mogliśmy zobaczyć podobny problem:

Zaczynając od tablicy liczb nieuporządkowanych, zwróć nową tablicę, dodając 1 do każdej wartości i sortując ją bez mutowania oryginału:

var numbers = [2, 4, 12, 6, 8, 29, 5, 10, 87, 11, 7];

function process(arr) {
    let newArr = arr.slice();
    newArr[0]++;
    for (let i = 1; i < newArr.length; i++) {
        const current = newArr[i] + 1;
        let leftIndex = i - 1;

        while (leftIndex >= 0 && newArr[leftIndex] > current) {
            newArr[leftIndex + 1] = newArr[leftIndex];
            leftIndex = leftIndex - 1;
        }
        newArr[leftIndex + 1] = current;
    }
    return newArr;
}

const newArray = process(numbers);


(używam sortowania przez wstawianie tylko dlatego, że łatwiej było go zaimplementować). Ten przykładowy kod nie jest tak naprawdę czytelny, ale jest wydajny i to dużo bardziej niż czytelny kod ES6, np. taki:

const process = (arr) => arr
    .map(num => num + 1)
    .sort((a, b) => a - b);

const newArray = process(numbers);


W rzeczywistości pierwszy kawałek kodu jest ~ 75% szybszy niż drugi, mimo że drugi jest bardziej czytelny i może nawet zostać uproszczony do jednego wiersza:

const newArray = numbers.map(num => num + 1).sort((a, b) => a - b);


Lub podzielona na funkcje pomocnicze dla lepszej czytelności:

const addOne = (n) => n + 1;
const asc = (a, b) => a - b;
const newArray = numbers.map(addOne).sort(asc);


Jest oczywiste, że kod ES6 (niezależnie od podejścia) jest dużo bardziej czytelny, dzięki czemu jest łatwiejszy do zrozumienia na pierwszy rzut oka. Dzięki czytelnemu kodowi, możemy szybciej wprowadzać nowych deweloperów do projektów, możemy łatwiej dzielić się naszym kodem i staje się on łatwiejszy do utrzymania.

Biorąc wszystko pod uwagę, wydajność w większości przypadków staje się zbędna. Właśnie dlatego ES6 ewoluował w ten sposób.

Ostateczne porównanie obu podejść:

źródło

W tym momencie prawdopodobnie zastanawiasz się: Co jeszcze jest mniej wydajne, ale bardziej czytelne?

Cóż, spójrzmy razem na kilka przypadków użycia.

Składnia rozwinięcia vs Object.assign()

Zacznijmy od następującego prostego problemu:
Skopiuj obiekt i dodaj nową właściwość do kopii

Rozwiązania:

const params = {...}; // filled Object

// ES6 - Spread syntax
var copy1 = { a: 2, ...params };

// Object.assign()
var copy2 = Object.assign({}, { a: 2 }, params);


Oba te podejścia wykonują zadanie, ale wszyscy możemy się zgodzić, że składnia rozwinięcia jest bardziej czytelna mimo że jest ~ 54% wolniejsza.

źródło

For loop vs Reduce

Problem:
Zsumuj wszystkie wartości z tablicy.

Rozwiązania, zacznijmy od klasycznego … loop:

const numbers = [2, 4, 12, 6, 8, 29, 5, 10, 87, 11, 7];

function arraySum(arr) {
    let sum = 0;
    for (let i = 0; i < arr.length; i++) {
        sum += arr[i]
    }
    return sum;
}

const sumOfNumbers = arraySum(numbers);


Przejdźmy teraz do wszechpotężnego reduce:

const numbers = [2, 4, 12, 6, 8, 29, 5, 10, 87, 11, 7];

const add = (a, b) => a + b;
const arraySum = (arr) => arr.reduce(add);

const sumOfNumbers = arraySum(numbers);


W tym przypadku reduce jest niezwykle kosztowny z punktu widzenia wydajności - jest ~ 96% wolniejszy!

źródło

For vs While vs Do while

źródło

Różnica jest prawie niezauważalna, ale mimo to w przypadku wątpliwości… wybierz klasycznego for loop.


Kiedy korzystać z czego?

Wow! Dobre sobie… Używam składni rozwinięcia, reduce itd. dla wszystkich moich operacji! Szczerze mówiąc, to trochę przygnębiające - obiecali mi czytelność bez utraty wydajności! Żądam spowrotem moich pieniędzy! (tryb paniki)

Nie panikujmy i przeanalizujmy sytuację. Więc kiedy powinienem czego użyć? Odpowiedź na powyższe pytanie jest prostsza, niż oczekiwałeś: Zależy.

Wracając do pierwszego przykładu, jeśli musimy: kopiować, dodawać i sortować macierz lub obiekt małych lub średnich rozmiarów… Następnie chcemy celować w czytelność, powinniśmy wykorzystać wszystkie dostępne zabawki z arsenału ES6.

W rzeczywistości prawie cały nasz kod można przepisać, koncentrując się na czytelności zamiast wydajności, oczywiście w zależności od projektu.

Spróbujmy zrobić listę.

Kiedy priorytetem powinna być czytelność

  • Gdy dane, z którymi mamy do czynienia, nie są zbyt duże,
  • Gdy aplikacja działa poprawnie pod względem szybkości, obciążenia itp.,
  • Podczas pracy w dynamicznym środowisku z wieloma nowicjuszami w projekcie,
  • Pisząc bibliotekę lub wtyczkę, która wymaga przeczytania dla zrozumienia.



Kiedy priorytetem powinna być wydajność

  • Gdy mamy do czynienia z dużymi danymi,
  • Gdy aplikacja jest powolna lub ma inne problemy z wydajnością,
  • Gdy projekt ma być skalowalny,
  • Podczas pracy nad projektem osobistym, koduj jak chcesz.


Więc jeśli chodzi o duże dane, unikaj stosowania reduce, filter, map, składni rozwinięcia itp. w tej części kodu, która dotyczy konkretnie tego obiektu lub tablicy.


Wnioski

Zamiast rzucać się od razu na najfajniejsze i najnowsze rozwiązania, powinniśmy cofnąć się o krok i przeanalizować, czy jest to wygodne dla naszego projektu i dla naszego przypadku.

Niewątpliwie nowe funkcje ES6 są błogosławieństwem i sprawiają, że codzienne kodowanie z JavaScript jest przyjemnością, ale jeśli zmagamy się z wydajnością, jeśli obsługujemy duże ilości danych... Powinniśmy ponownie przemyśleć, których narzędzi używać.

Przy klasie wagi ciężkiej wybieram mniej czytelny, ale wydajniejszy kod 💪 W przypadku ogromnych danych, wybieram staranny research i wdrożenie najbardziej wydajnych algorytmów dla tego zadania. We wszystkich innych przypadkach, wybieram piękną czytelność ES6! ❤

Zastrzeżenie

Wyniki testu pokazane w tym poście mogą się nieznacznie różnić w zależności od obciążenia przeglądarki, systemu operacyjnego i serwera.

Masz coś do powiedzenia?

Podziel się tym z 120 tysiącami naszych czytelników

Dowiedz się więcej
Rocket dog