Клиентская оптимизация / [Из песочницы] JavaScript. Оптимизация: опыт, проверенный временем

в 20:24, , рубрики: Новости, метки: , , , ,

Предисловие

Давно хотел написать. Мысли есть, желание есть, времени нету… Но вот нашлось, так что привет, Хабра.
Здесь я собрал все идеи, которые помогали и помогают в разработке веб-приложений. Для удобства я разбил их на группы:Память

Оптимизация операций

Выделение критических участков

Циклы и объектные свойства

Немножко о DOM

DocumentFragment как промежуточный буфер

О преобразованиях в объекты

Разбитие кода

События перетаскивания

Другие советы

Память

Хоть это и не должно волновать клиентского программиста, но не забываем, что память всё-таки не бесконечна и когда-нибудь может закончиться, например, когда запущено несколько массивных программ: офис, графический редактор, компиляция большой программы и др. Несмотря на то, что приведенный пример тривиален, у меня действительно такое случилось, хоть и не из-за браузера, но он тоже сыграл свою роль: 1,3 Гб оперативы (отладчик, около 30 вкладок), начались тормоза по перегрузке страниц ОП в файл подкачки.
Чтобы уменьшить расход памяти, я предлагаю несколько способов:
1) уменьшение числа локальных переменных.
Вы спросите, что это значит? Объясняю, на своей практике видел, как «code monkey»-студенты писали подобный код:
(function init(){ //здесь и далее я буду использовать функции-обёртки
for(var i=0,n=1;i<10;i++) //суть не в действии внутри цикла n+=n; alert(n); //выводим результат for(var i=0,m=1;i<10;i++) m*=m; alert(m); })(); Может быть, вы сразу и не видите, в чем фишка, но: зачем создавать новые переменные, если у нас есть использованные и уже хранящие в себе совершенно ненужные значения старые переменные? В данном примере для нормального решения необходимо заменить все m на n, что сэкономит память. Этот метод лучше всего проявляет себя в рекурсивных функциях, потому что каждый вызов такой функции провоцирует создание и, обратите внимание, удаление локальных переменных после завершения работы функции, что тоже требует процессорного времени и памяти. Для наглядного восприятия можно привести аналогию со шкафчиками: у вас есть 6 шкафчиков, три из которых могут быть заполнены; зачем тогда вам ещё три шкафчика, если в таком случае вам придётся открывать все 6, а потом и закрывать все 6? 2) уменьшение числа замыканий. Замыкания вызывают ощутимый расход памяти (3 Мб на 1000 объектов для хрома, возможно, в новых версиях другой объем), поэтому используйте их как можно реже. Я использую их в двух случаях:Необходимо скрыть данные внутри некоторого интерфейса, не дать доступа извне; При рекурсии, когда нужно сделать какие-то пометки в одном общем объекте (например, при обходе HTML занести в массив все узлы, которые имеют пользовательское свойство «dragndrop») в том случае, если выборка по селекторам не подходит. Оба случая подразумевают какие-то частные, уникальные, случаи. Имеется в виду, что создаются единичные интерфейсы. Пример первого случая: (function init(){ var INTERNAL_NUMBER=0;//замкнутая переменная return { get:function get(){return INTERNAL_NUMBER;},//функция, возвращающая значенеи замкнутой переменной set:function set(value){ //функция, фильтрующая передаваемые значения и устанавливающая замкнутую переменную if(typeof value==”number”) INTERNAL_NUMBER=value; return INTERNAL_NUMBER; } } })(); Вот таким образом я и создаю ЕДИНИЧНЫЕ интерфейсы. Если с первым уже, я думаю, всё понятно, то второй случай следует пояснить: фактически он относится к предыдущему пункту, потому что мы заменяем переменную, которую можно было передать в качестве аргумента в функцию, замыканием, тем самым уменьшая количество локальных переменных внутри этой функции и в то же время соблюдаем принцип минимума замыканий: это замыкание характерно только для это рекурсивной функции (хотя, это уже как вы захотите), ведь при рекурсии используется одна и та же функция (новые замыкания не создаются). Пример: (function init(){ var found=[]; (function traverse(html){ for(var i=html.firstChild;i;i=i.nextSibling) arguments.callee(i); if(typeof html.dragndrop==”object”) found.push(html); })(document.body); return found; })(); Как видно из примера, в рекурсивной функции содержатся 2 локальные переменные (html, i) вместо трёх (html, i, found). На практике выигрыш в скорости несущественен (по крайней мере, от замыкания всего лишь одной переменной), зато даёт знать о себе выигрыш в памяти. И, пожалуйста, не упрекайте за nextSibling, а не за nextElementSibling, все делалось в первую очередь ради разъяснения сути замыкания внутри рекурсивной функции. ВНИМАНИЕ: Никогда не делайте замыкания посредством цикла — это вызывает чрезмерный расход памяти. Исключения составляют случаи, когда логика скрипта требует безоговорочного сокрытия данных (но в любом случае, если у меня есть отладчик, я и туда доберусь? ). Пример неправильного использования замыканий: function addEvents2(divs) { for(var i=0; i

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js