14 советов по оптимизации JavaScript-кода

0
45
views

Перевод статьи «14 JavaScript Code Optimization Tips for Front-End Developers».

Photo by form PxHere

JavaScript стал одним из самых популярных языков программирования всех времен. Он используется практически на 96% всех сайтов в мире (согласно W3Tech).

Главное, что вы должны знать о вебе, это что вы не контролируете спецификации «железа» и то, с каких устройств пользователи будут заходить на ваш сайт. Конечный пользователь может зайти как с самого современного устройства, так и с довольно старого. И интернет-связь у него тоже может быть как очень хорошей, так и совсем плохой. Все это означает, что вам нужно следить за тем, чтобы ваш сайт был как можно лучше оптимизирован: только так вы сможете удовлетворить запросы максимального числа пользователей.

В этой статье вы найдете несколько советов о том, как лучше оптимизировать код на JavaScript, чтобы достигнуть более высокой производительности.

1. Удаляйте неиспользуемый код и функционал

Чем больше кода в вашем приложении, тем больше данных нужно передавать клиенту и тем больше времени потребуется браузеру на анализ и интерпретацию кода.

Порой вы можете включать в приложение функции, которые вообще не используются. Лучше держать такой лишний код в среде разработки и не отправлять его в продакшен. Таким образом вы избавите браузер пользователя от ненужной работы.

Всегда спрашивайте себя, является ли функция, фича или кусок кода необходимыми.

Удалить ненужный код можно вручную, но можно и воспользоваться специальными инструментами, такими как Uglify или гугловский Closure Compiler. Для удаления ненужного кода из приложения можно даже применять специальную технику — tree shaking («тряска дерева»). Ее предоставляют упаковщики, такие как Webpack. Почитать больше о tree shaking можно здесь. Для удаления неиспользуемых npm-пакетов можно воспользоваться командой npm prune. О ней можно почитать в документации NPM.

2. Кэшируйте, когда это только возможно

Кэширование увеличивает скорость и производительность вашего сайта за счет уменьшения задержки и снижения сетевого трафика. То есть, благодаря кэшированию на отображение ресурса на экране нужно меньше времени. Помочь в этом вам могут Cache API или HTTP caching.

Вы можете спросить, что будет, если ваш контент изменится. Механизмы кэширования, упомянутые выше, умеют управлять кэшем и заново генерировать его при определенных условиях, например, если публикуется новый контент.

3. Старайтесь не допускать утечек памяти

Будучи высокоуровневым языком, JS заботится о некоторых низкоуровневых вещах, таких как управление памятью. Сборка мусора — процесс, общий для большинства языков программирования. Грубо говоря, сборка мусора — это просто сбор и освобождение памяти, которая была выделена объектам, но в настоящее время не используется ни в одной части нашей программы. В таких языках программирования, как C, разработчик сам должен заботиться о распределении и освобождении памяти, используя функции malloc() и dealloc().

Несмотря на то, что в JavaScript сборка мусора осуществляется автоматически, могут быть случаи, когда это не идеальное решение. В JavaScript ES6 появились «более слабые» собратья Map и SetWeakMap и WeakSet (англ. weaker — «более слабый»). Эти «более слабые» аналоги содержат «слабые» ссылки на объекты. Они позволяют собирать ненужные значения и предотвращать утечки памяти. О WeakMap и WeakSet вы можете почитать, например, здесь (в оригинале ссылка на англоязычную статью).

4. Старайтесь выходить из циклов пораньше

Прохождение больших циклов занимает много драгоценного времени. Вот почему всегда следует стремиться выходить из цикла как можно раньше. В этом вам помогут ключевые слова break и continue. Не забывайте, что именно на вас лежит ответственность за написание максимально эффективного кода.

Если в примере, приведенном ниже, не использовать break, ваш код будет проходить цикл 1000000000 раз, а это определенно излишняя нагрузка.

let arr = new Array(1000000000).fill('----');
arr[970] = 'found';
for (let i = 0; i < arr.length; i++) {
  if (arr[i] === 'found') {
        console.log("Found");
        break;
    }
}

А если бы в следующем примере не было continue, мы запустили бы функцию 1000000000 раз. Благодаря continue мы обрабатываем только четные элементы массива, а это сокращает работу цикла практически наполовину.

let arr = new Array(1000000000).fill('----');
arr[970] = 'found';
for (let i = 0; i < arr.length; i++) {
  if(i%2!=0){
        continue;
    };
    process(arr[i]);
}

Почитать больше о циклах и производительности можно здесь.

5. Минимизируйте число случаев, когда переменные вычисляются

Чтобы снизить количество вычислений переменных, можно использовать замыкания. Если говорить по-простому, замыкания в JavaScript предоставляют доступ к области видимости внешней функции из внутренней функции. Замыкания создаются при создании функции, а не при ее вызове. Внутренние функции будут иметь доступ к переменным внешней области видимости даже после того, как внешняя функция вернется.

Давайте рассмотрим пару примеров, чтобы увидеть все это в действии.

function findCustomerCity(name) {
  const texasCustomers = ['John', 'Ludwig', 'Kate']; 
  const californiaCustomers = ['Wade', 'Lucie','Kylie'];
  
  return texasCustomers.includes(name) ? 'Texas' : 
    californiaCustomers.includes(name) ? 'California' : 'Unknown';
};

Если мы будем вызывать эти функции несколько раз, при каждом вызове будет создаваться новый объект. И для каждого вызова будет зря выделяться память для переменных texasCustometrs и californiaCustomers.

Используя решение с замыканиями, мы можем инициализировать переменные единожды. Обратите внимание на следующий пример:

function findCustomerCity() {
  const texasCustomers = ['John', 'Ludwig', 'Kate']; 
  const californiaCustomers = ['Wade', 'Lucie','Kylie'];
  
  return name => texasCustomers.includes(name) ? 'Texas' : 
    californiaCustomers.includes(name) ? 'California' : 'Unknown';
};
let cityOfCustomer = findCustomerCity();
cityOfCustomer('John');//Texas
cityOfCustomer('Wade');//California
cityOfCustomer('Max');//Unknown

В этом примере, благодаря замыканиям, внутренняя функция, возвращаемая в переменную cityOfCustomer, имеет доступ к константам внешней функции findCustomerCity(). И при каждом вызове внутренней функции (с именем, переданным в качестве параметра) не возникает необходимости инициализировать эти константы снова.

Если хотите узнать больше о замыканиях, я советую почитать этот пост.

6. Минимизируйте доступ к DOM

Доступ к DOM медленный — по сравнению с другими предложениями JavaScript. Если вы внесете в DOM изменения, которые запустят перерисовывание макета, это может очень сильно замедлить работу.

Чтобы сократить число случаев доступа к элементу DOM, осуществляйте доступ единожды, а затем используйте результат в качестве локальной переменной. Когда все необходимое будет сделано, не забудьте удалить значение переменной, установив его в null. При этом станет возможным процесс сборки мусора, а в результате будет предотвращена утечка памяти.

7. Сжимайте свои файлы

Использование методов сжатия (например, Gzip) может уменьшить размер ваших JavaScript-файлов. Уменьшение веса файлов повышает производительность сайта, поскольку браузеру приходится меньше всего скачивать.

Сжатие может уменьшить размер файла чуть ли не на 80%. Почитать больше на эту тему можно здесь.

Photo by JJ Ying on Unsplash

8. Минимизируйте ваш итоговый код

Некоторые считают, что минимизация и сжатие это одно и то же. Но это разные вещи. При сжатии специальные алгоритмы меняют итоговый размер файла. При минимизации из JavaScript-файлов удаляются комментарии и лишние пробелы. Для этого процесса есть много инструментов и пакетов (можно поискать в сети). Минимизация стала стандартной практикой для оптимизации страниц, а также основной составляющей оптимизации фронтенда.

Минимизация может уменьшить размер ваших файлов на 60%. Почитать больше можно здесь.

9. Используйте throttle и debounce

Использование этих двух техник позволяет строго следить за тем, сколько раз событие обрабатывается кодом.

Тротлинг — это определение максимального числа запусков функции в определенный промежуток времени. Например, «выполняй функцию onkeyup не чаще одного раза в 1000 миллисекунд». Это означает, что даже если вы нажимаете 20 клавиш в секунду, событие будет срабатывать только один раз в секунду. А это уменьшит нагрузку на ваш код.

Debouncing — указание минимального промежутка времени после вызова функции, через который функция может быть выполнена снова. Другими словами, «выполняй эту функцию, только если прошло 600 миллисекунд с тех пор, как она вызывалась последний раз». Это означает, что если с последнего вызова этой функции еще не прошло 600 миллисекунд, он ане будет вызываться повторно.

Понятное объяснение можно почитать здесь.

Вы можете реализовать debounce— и throttle-функции самостоятельно, а можете импортировать их из библиотек, таких как Lodash и Underscore.

10. Старайтесь не использовать ключевое слово delete

Ключевое слово delete используется для удаления свойства из объекта. Но в отношении производительности этого ключевого слова зафиксировано несколько жалоб. Посмотреть можно здесь и здесь. В будущих обновлениях, вероятно, это исправят.

В качестве альтернативного варианта можно просто устанавливать для нежелательного свойства значение undefined.

const object = {name:"Jane Doe", age:43};
object.age = undefined;

Вы также можете использовать объект Map, поскольку его метод delete работает быстрее (так пишет Bret).

11. Используйте асинхронный код, чтобы предотвратить блокировку потоков

Вы, должно быть, знаете, что JavaScript по умолчанию является синхронным, а также однопоточным. Но бывают случаи, когда вашему коду требуется много времени для вычислений. Синхронность предполагает, что этот фрагмент кода будет блокировать выполнение других операторов кода до тех пор, пока сам не будет выполнен. Это снизит вашу производительность в целом.

Но мы можем предотвратить эту ситуацию, внедрив асинхронный код. Раньше асинхронный код писался в форме обратных вызовов, но в ES6 появился новый стиль обработки асинхронного кода. Этот новый стиль был назван promises. Вы можете узнать больше об обратных вызовах и promises в официальной документации MDN.

Но погодите-ка…

«JavaScript по умолчанию является синхронным, а также однопоточным».

Как же получится запускать один поток и при этом запускать асинхронный код? Многие люди этого не понимают. Но это возможно благодаря движку JavaScript, который работает под капотом браузера. Движок JavaScript это программа или интерпретатор, который выполняет код JavaScript. Сам этот движок может быть написан на самых разных языках. Например, движок V8, который работает в браузерах Chrome, написан на C++, а движок SpiderMonkey в браузерах Firefox написан на C и C++.

Эти движки JavaScript могут управлять решением задач в фоне. Как написано в этой статье, стек вызовов распознает функции веб-API и передает их в браузер. Как только эти задачи завершены браузером, они возвращаются и помещаются в стек как обратный вызов.

У вас могли возникать вопросы по поводу того, как все происходит с Node.js, ведь там браузер не приходит на помощь. Но фактически тот же движок V8, управляющий Chrome, управляет также и Node.js. Вот отличный пост на эту тему.

Photo by form PxHere

12. Используйте Code Splitting

Если у вас есть опыт пользования Google Light House, вам знаком такой показатель как «first contentful paint» (время, за которое первый текст или изображение появились на экране). Это один из шести показателей, отслеживаемых в разделе «Performance» отчета Lighthouse.

First Contentful Paint (FCP) замеряет, сколько времени нужно браузеру, чтобы отобразить первую часть контента DOM после того как пользователь перешел на вашу страницу. Изображения, небелые элементы <canvas> и SVG на вашей странице считаются содержимым DOM, а все, что внутри iframe, не считается.

Один из лучших способов добиться хороших показателей FCP, — использовать code splitting («разделение кода»). Code splitting это подход, при котором вы вначале отсылаете пользователю только самые необходимые модули. Это сильно снижает передаваемую изначально полезную нагрузку.

Популярные упаковщики модулей, например Webpack, предоставляют функционал code splitting. Также в этом деле вам могут помочь нативные ES-модули, почитать о которых можно здесь.

13. Используйте async и defer

В современных браузерах скрипты более интенсивны, чем HTML, они больше по размеру и требуют больше времени для обработки. По умолчанию браузер должен подождать, пока скрипт скачается, выполнить его, а затем обрабатывать остальную страницу.

В результате раздутый скрипт может заблокировать загрузку всей страницы. Чтобы этого избежать, JavaScript предоставляет нам две техники, известные как async и defer. Вы можете просто добавлять эти атрибуты к тегам <script>.

Async — это когда вы говорите браузеру загружать скрипт, не затрагивая рендеринг. То есть, страница не ждет async-скрипты; контент обрабатывается и отображается.

Defer — это когда вы говорите браузеру загружать скрипт после окончания рендеринга.

Если вы указываете и async, и defer, в современных браузерах предпочтение отдается async. Более старые браузеры, поддерживающие defer, но не поддерживающие async, откатятся к defer.

Использование этих двух атрибутов может существенно уменьшить время загрузки страницы. По данной теме очень советую почитать вот этот пост.

14. Используйте Web Worker-ы для запуска ресурсоемких задач в фоне

Web Worker-ы позволяют запускать скрипты в фоновых потоках. Если у вас есть какие-то ресурсоемкие задачи, вы можете передать их web worker-м, которые будут выполнять их без вмешательства в пользовательский интерфейс. После создания web worker-а он сможет коммуницировать с JavaScript-кодом путем отправки сообщений обработчику событий, указанному в коде. Узнать больше можно в документации MDN.

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here