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

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

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

Начнем с истории

Это был 2014 год, и я разрабатывал интерфейсные приложения для киосков с помощью Angular.js. В то время Angular.js был очень популярен. React еще не появился на сцене, и не было причудливых альтернатив HTML, таких как JSX или виртуальный DOM. В те дни, если вы хотели разработать что-то для браузера, вам приходилось замарать руки и использовать семантический HTML, <template> или простой механизм шаблонов. Google решил поддержать Angular.js, и это было огромно. Была только первая основная версия Angular.js (не Angular), сияющая на сцене, и множество чрезмерно используемых инъекций зависимостей, таких как AMD (определение асинхронного модуля)

Superheroic AngularJS, интерфейсный фреймворк Google, выиграл битву javascript. Среди разработчиков javascript он становится чрезвычайно популярным способом создания веб-приложений. Он используется для создания одностраничных приложений (SPA). Он предоставляет разработчикам AngularJS уникальную возможность создавать веб-сайты с быстрой загрузкой, обеспечивая лучший пользовательский интерфейс без перезагрузки страницы, а также помогая компаниям максимизировать преимущества и удовлетворенность клиентов.

Статья с сайта 9series.com, написанная в 2016 году

Конечно, как только Facebook продвигал React и его виртуальный механизм DOM с помощью JSX, все начало меняться.

Недостаточно памяти

Проблема использования памяти JavaScript — это не просто проблема, которую нужно решать в продакшене, это проблема, которую следует решать на каждом этапе процесса разработки.

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

Понимание проблемы

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

Приложение React было разработано для работы на стационарном устройстве, таком как банкомат или киоск. Это приложение было создано для предоставления пользователям определенных услуг на открытом воздухе. На устройстве был установлен браузер, и приложение работало как одностраничное приложение в Chrome, используя ресурсы браузера Chrome.

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

Так же, как важно опорожнить ведро, чтобы освободить место для большего количества воды, важно реализовать циклы обновления, чтобы освободить место для большего объема памяти для приложения.

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

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

Предотвращение утечек

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

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

Рассмотрение произвольных типов

`WeakMap` не создает сильных ссылок на свои ключи и не имеет ссылок на свои ключи. Это означает, что ключи WeakMap могут быть удалены сборщиком мусора, если на них нет других ссылок.

const weakMap = new WeakMap();
weakMap.set(data, { processed: true });

Слушатели и интервалы

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

// Set up event listener
const element = document.getElementById('my-element');
element.addEventListener('click', handleClick);

// Set up interval
const intervalId = setInterval(() => {
  // Do something...
}, 1000);

// Clean up event listener and interval
function cleanup() {
  element.removeEventListener('click', handleClick);
  clearInterval(intervalId);
}

Закрытия

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

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

function createCounter() {
  let count = 0;

  return function() {
    count++;
    return count;
  }
}

const counter = createCounter();

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

Да, пожалуйста, отреагируйте

// Define handleClick function outside of component
function handleClick(event) {
  // Handle click event...
}

function MyComponent(props) {
  return (
    <button onClick={handleClick}>
      Click Me
    </button>
  );
}

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

Не лучше ли было бы определять методы извне и вызывать их в компоненте только при необходимости, а не генерировать их для каждого повторяющегося компонента?

Инфраструктура

Не думайте, что трава всегда зеленее

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

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

Наблюдение за приложением помогает нам лучше понять его, поскольку позволяет нам собирать доказательства и подтверждать наши предположения.

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

Решите, как вы начнете гонку

Одним из ключевых преимуществ использования Node.js или любого серверного языка в качестве отправной точки является возможность мониторинга низкоуровневых элементов, таких как ЦП и память.

При создании javascript-приложения важно учитывать выбор отправной точки. Использование node.js в качестве отправной точки вместо SPA `index.html` может предоставить большое количество информации и возможностей для приложения. Одним из ключевых преимуществ использования Node.js или любого серверного языка в качестве отправной точки является возможность отслеживать низкоуровневую информацию, такую ​​как ЦП и память. Это может иметь решающее значение для оптимизации производительности приложения и обеспечения его способности справляться с любыми потенциальными скачками спроса или использования.

Простота реализации решений

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

Это решение не требует сложных функций, таких как автоматическое масштабирование, самовосстановление и балансировка нагрузки. Использование диспетчера процессов, такого как PM2, может предоставить простое решение для выполнения необходимых задач и обеспечения бесперебойной работы приложения. PM2 предлагает простые в использовании команды и конфигурации, которые могут помочь в управлении приложением в производственной среде. Если вы предпочитаете больший контроль и власть над своим приложением, вы можете использовать инструмент оркестровки вместо диспетчера процессов.

Для приложений React, созданных CRA

pm2 serve - spa
module.exports = {
  script: "serve",
  max_memory_restart: '300M',
  env: {
    PM2_SERVE_PATH: '.',
    PM2_SERVE_PORT: 8080,
    PM2_SERVE_SPA: 'true',
    PM2_SERVE_HOMEPAGE: './index.html'
  }
}

Для сложных приложений

Чтобы использовать подход «Обслуживание статического файла через HTTP», вам просто нужно включить следующий код

в вашем приложении:

const pm2 = require('pm2');

pm2.connect(err => {
  if (err) {
    console.error(err);
    process.exit(2);
  }

  pm2.start({
    script: 'static-server.js', // The script to run
    exec_mode: 'cluster', // Run the script in cluster mode
    instances: 2, // Use two instances
    max_memory_restart: '100M' // Restart the instances if they use more than 100MB of memory
  }, (err, apps) => {
    pm2.disconnect(); // Disconnect from PM2
    if (err) throw err
  });
});

Этот код запустит два экземпляра скрипта с именем static-server.js, который будет отвечать за обслуживание статических файлов. Экземпляры будут работать в кластерном режиме, что означает, что они смогут обрабатывать несколько запросов одновременно. Кроме того, код включает максимальное ограничение памяти в 100 МБ, что означает, что экземпляры будут автоматически перезапущены, если они превысят этот предел.

Дополнительную информацию можно найти на веб-сайте pm2

В итоге

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