Поскольку современные браузеры стали поддерживать больше возможностей, а веб-индустрия стремительно перемещается в сторону мобильных устройств, появилась необходимость писать компактный и оптимизированный код, который не заставит долго ждать пользователя, пока загрузится сайт. Front-end хорош тем, что в нем содержится много простых стратегий и конвенций кода, которые мы можем использовать для обеспечения оптимальной производительности. В этой статье мы собрали 9 простых советов, которые помогут с оптимизацией кода.

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

1. Используйте DocumentFragments или innerHTML вместо массовых внедрений в элементы


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

К примеру, у нас есть ul элемент в пределах страницы, который изменяется через JavaScript после вызова AJAX, чтобы получить элемент списка JSON. Зачастую разработчики будут делать что-то вроде этого:

 var list = document.querySelector('ul');
 
 ajaxResult.items.forEach(function(item) {
 
    // создадим элемент списка li
	
    var li = document.createElement('li');
	
    li.innerHTML = item.text;
	
   // Здесь произойдут разнообразные структурные операции с нашим элементом,
   
   // например: добавление классов, модификация атрибутов и т.д. 
   
   // добавим элемент li к родительскому элементу ul
   
  list.apppendChild(li);
  
 });

Код выше показывает неправильный путь заполнения ul элементами – скорость дополнения DOM – дерева замедляется. Если вы действительно хотите использовать document.createElement и обработать элемент как узел дерева, было бы более производительно вместо этого использовать DocumentFragement.

DocumentFragement является так званым «мини-родителем», «облачным хранилищем» для его дочерних элементов. В нашем примере следует считать DocumentFragement как невидимый ul элемент, который находится за пределами DOM. Это позволит хранить Ваши узлы виртуально, пока они не будут введены в DOM. Первоначальный образец кода будет оптимизирован с помощью DocumentFragement следующим образом:

var frag = document.createDocumentFragment();
 
 ajaxResult.items.forEach(function(item) {
 
   // создадим элемент списка li
	
   var li = document.createElement('li');
	
   li.innerHTML = item.text;
	
    // Здесь произойдут разнообразные структурные операции с нашим элементом,
	
    // например: добавление классов, модификация атрибутов и т.д. 
	
    // *добавим наш элемент в фрагмент, вместо родительского элемента*
	
    frag.appendChild(li);
	
 });
 
   // В конечном итоге, добавим все элементы в DocumentFragment	
   
 document.querySelector('ul').appendChild(frag);

Добавление дочерних элементов в отдельный DocumentFragement, а затем добавление данного фрагмента к родительскому списку используется с помощью только одной операции DOM. Таким образом, это получается намного быстрее, чем массовые внедрения. Если вам не нужно работать с такими элементами списка как узлы, то более выгодно строить HTML с помощью строк:

var htmlStr = '';
 
 ajaxResult.items.forEach(function(item) {
 
    // Построим строку
	
    htmlStr += '<li>' + item.text + '</li>';
	
 });

   // Комбинация элементов списка через innerHTML
   
 document.querySelector('ul').innerHTML = htmlStr;

Таким образом, внедряя созданную строку через innerHTML по-прежнему остается только одна операция DOM и меньше кода даже чем у DocumentFragment метода.

2. Часто выполняемые события / методы


В большей части работы необходимо добавлять обработку событий, которые будут происходить довольно часто, во время взаимодействия с пользователем. К примеру события window's resize или onmouseover. Если обработка данных событий слишком ресурсозатратная, Вы можете дать большую загрузку на браузер, а это, в свою очередь, приведет к плохим последствиям на стороне пользователя. Вот откуда пришел метод debouncing. Debouncing будет ограничивать количество раз выполнения функции в пределах заданного временного интервала. Вот пример использования функции debouncing:

 // При работе с UnderscoreJS
 
 function debounce(func, wait, immediate) {

    var timeout;
	
    return function() {
	
        var context = this, args = arguments;
		
        var later = function() {
		
            timeout = null;
			
            if (!immediate) func.apply(context, args);
			
        };
		
        var callNow = immediate && !timeout;
		
        clearTimeout(timeout);
		
        timeout = setTimeout(later, wait);
		
        if (callNow) func.apply(context, args);
		
    };
  }

  // Добавим обработку события resize но не чаще чем через каждые 300 миллисекунд

  window.addEventListener('resize', debounce(function(event) {

   // Обработать событие resize (как Вам вздумается)

 }, 300));

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

3. Статичный кэш, не существенный контент в веб хранилище


API-интерфейсы по типу Web хранилищ стали огромным усилением и упрощением работы с API Cookie, которую разработчики использовали в течение многих лет. Одна из стратегий, которую можно использовать при работе с памятью для хранения несущественных данных был статичный контент. Здесь подразумеваются фрагменты HTML, содержание статьи, которая была загружена с помощью AJAX и других разнообразных методов, которые нужно запрашивать не более одного раза. Ниже показан небольшой код на JavaScript для работы с веб хранилищем:

this[key] = value;

        }
    };

  return {
	
    get: function(key) {
		
        return this.isFresh(key);
			
    },
	
    set: function(key, value, minutes) {
		
        var expDate = new Date();
			
        expDate.setMinutes(expDate.getMinutes() + (minutes || 0));

        try {
			
            cacheObj.setItem(key, JSON.stringify({
				
                value: value,
					
                expires: expDate.getTime()
					
                }));
            }
			
            catch(e) {}
			
        },
		
    isFresh: function(key) {
		
        // Возвращаем значение или false
			
        var item;
			
        try {
			
            item = JSON.parse(cacheObj.getItem(key));
				
            }
			
        catch(e) {}
			
            if(!item) return false;

        // Высчитаем дату
			
            return new Date().getTime() > item.expires ? false : item.value;
        }
     }
 });

Этот плагин с помощью базовых get и set методов, либо через isFresh метод, проверяет, актуально ли сохраненное значение.

 require(['storage'], function(storage) {
 
    var content = storage.get('sidebarContent');
	
    if(!content) {
	
        // Выполним AJAX запрос для получения контента

        // ... потом хранилище вернет контент через определенное время
		
        storage.set('sidebarContent', content, 60); 
    }
	
 });

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

4. Использование curl.js


Асинхронная загрузка ресурсов стала популярной благодаря XMLHttpRequest (объект, который разрешен для AJAX), чтобы не блокировать загрузку ресурсов, уменьшить время загрузки страницы и позволить загрузить содержимое без ее перезагрузки.

Здесь будет использоваться замечательный плагин Джона Ханна curl.js. Curl загрузчик – это типичный асинхронный загрузчик, но немного с другими параметрами конфигурации, полезными плагинами и многим другим. Вот несколько примеров использования curl.js:

// Базовое использование
 
	curl(['social', 'dom'], function(social, dom) {
	
    dom.setElementContent('.social-container', social.loadWidgets());
	
 });

 // Определяет модуль, который использует Google Analytics

 define(["js!//google-analytics.com/ga.js"], function() {
 
    // Возвращает Google Analytics контроллер
	
    return {
	
        trackPageView: function(href) {
		
            _gaq.push(["_trackPageview", url]);
			
        },
        trackEvent: function(eventName, href) {
		
            _gaq.push(["_trackEvent", "Interactions", eventName, "", href || window.location, true]);
			
         }
     };
 });

 // Единожды загружает JavaScript файл без обратной связи

 curl(['js!//somesite.com/widgets.js']);

 // Загрузит JavaScript and CSS файлы в модуле
 
 curl(['js!libs/prism/prism.js', 'css!libs/prism/prism.css'], function() {
 
    Prism.highlightAll();
	
 });

 // Загружает a URL (через an AJAX запрос)
 
 curl(['text!sidebar.php', 'storage', 'dom'], function(content, storage, dom) {
 
    storage.set('sidebar', content, 60);
	
    dom.setElementContent('.sidebar', content);
	
 });

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

Например, загружаем только виджеты Twitter, Facebook и Google Plus, если DIV с классом CSS-social существует. Если данного блока нет, виджеты мы не грузим. Следовательно, используя принцип «проверить – если это нужно перед загрузкой – то грузим» спасет пользователя от закачки нескольких совершенно ненужных килобайт.

5. Используйте Array.prototype.join вместо конкатенации строк


Одна микро-оптимизация на стороне клиента с помощью Array.prototype.join заменяет обычную конкатенацию. К примеру, в пункте №1 (см. выше), было использована элементарная конкатенация строк. А вот так это будет выглядеть после данной оптимизации

var items = [];

 ajaxResult.items.forEach(function(item) {
 
    // Построим строку
	
    items.push('<li>', item.text, '</li>');
	
	});

    // Комбинация элементов списка через innerHTML
	
 document.querySelector('ul').innerHTML = items.join('');

6. Использование CSS Animations, когда это возможно


Можно утверждать, что рост библиотек JavaScript, таких как JQuery и MooTools привели к сложным анимационным оформлениям. Сегодня многие разработчики все еще используют JavaScript, чтобы оживить элементы, несмотря на то, что соответствующие браузеры поддерживают CSS анимации через transform и keyframe.

CSS анимации более эффективны, чем анимации JavaScript. CSS анимации также имеют дополнительное преимущество (обычно) — намного меньше кода. Многие анимации CSS обрабатываются GPU, и, таким образом, более сглажены. К примеру:

.myAnimation {

    animation: someAnimation 1s;
	
    transform: translate3d(0, 0, 0); /* ускорение */
	
 }

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

7. Используйте делегирование событий


Представьте себе неупорядоченный список ul, который может содержать любое количество дочерних элементов li, и каждый li должен что-то сделать по щелчку мыши. Вы можете добавить обработчики событий к отдельным элементам, но что, если элементы будут добавляться или удаляться часто? Вам предстоит иметь дело с добавлением и удалением обработчиков событий, а также самих элементов. Вот где нам понадобится делегирование событий.

Делегирование события заменяет необходимость добавления обработчиков событий к отдельным элементам вместо размещения одного обработчика событий для родителя. Когда это событие срабатывает, event.target проверяет, выполняются ли условия для вызова функции обработки данного события.

Вот невероятно простой пример того, что происходит:

// Берем элемент и добавляем к нему обработчик события click...
 
 document.querySelector('#parent-list').addEventListener('click', function(e) {
 
    // e.target кликабельный элемент!
	
    // Если это необходимый нам элемент
	
    if(e.target && e.target.tagName == 'LI') {
	
        // Необходимый элемент найден! Действуем!
		
    }
 });

Все фреймворки JavaScript обеспечивают соответствие делегирования селектора. Дело в том, что Вы избегаете размещения обработчиков событий на отдельные элементы, установив один на родителя. Этот метод более эффективен и менее затратен.

8. Используйте data URL’s, вместо SRC’s


Скорость загрузки страницы напрямую зависит от использования спрайтов и написания компактного кода. Число запросов отсылаемых данной страницей также играют роль в производительности. Минимизация запросов помогает Вашему сайту грузиться быстрее и один из способов устранения запросов (не считая использования спрайтов) использовать Data URL вместо атрибутов image SRC:

<!—До… -->
 
 <img src="/images/logo.png" />

 <!-- …После -->
 
 <img src="data: image/jpeg;base64,/9j/4AAQSkZJRgABAgAAZABkAAD/7AARRHVja3kAAQAEAAAAPAAA/+4ADkFkb2
 
 JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoKDBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fH
 
 x8fHwEHBwcNDA0YEBAYGhURFRofHx8fHx8fHx8fHx8fHx8fH ...." />

Конечно же размер страницы увеличится, но вы уменьшаете время ожидания и количество запросов к серверу. Большинство браузеров сейчас поддерживают Data URL’s в CSS файлах (для фоновых изображений) так что этот метод в целом широко может быть использован.

9. Используйте Media Queries для загрузки фоновых изображений необычных размеров.


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

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

/* стандартно на Desktop */
 
 .someElement { background-image: url(sunset.jpg); }

 @media only screen and (max-width : 1024px) {
 
    .someElement { background-image: url(sunset-small.jpg); }
 }

Фрагмент выше загружает изображение меньшего размера, если пользователь использует мобильное устройство (имеется ввиду размер файла, не соотношение сторон). Таким способом, достаточно отрисовать спрайт меньшего размера, что избавит от необходимости проводить манипуляции с изображениями програмным методом или разрезать спрайт для мобильных устройств и т.д.

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

Небольшие обновления, большие успехи

Очень часто разработчики ставят себя и свой стиль кода на первое место вместо того, чтобы подумать сначала о своих пользователях. Много небольших обновлений построены для улучшения комфорта пользователя, таким образом, каждый бит оптимизации загрузки улучшает впечатление о Вашем сайте. Google выпустил достаточно удобное расширение для своего браузера, которое поможет Вам проанализировать скорость загрузки Вашего сайта и узнать, что еще можно оптимизировать, для повышение загрузки страницы. Называется данное расширение PageSpeed Insights. Использовать его можно через стандартную панель разработчика. Данный виджет покажет Вам минимальный анализ Вашего портала и даст советы по его оптимизации, к примеру:



В любом случае, оптимизировать сайт нужно максимально. Чем быстрее он будет работать, тем больше будут удовлетворены пользователи, тем больше заработает сайт. К сожалению, далеко не все задумываются об этом. Это даже видно по спросу: нам часто заказывают аудиты юзабилити или Back-End, и редко кто просит провести аудит Front-End, хотя он абсолютно не сложно делается и может дать хорошее поле для улучшений.
Поделиться с друзьями
-->

Комментарии (15)


  1. bromzh
    01.12.2016 14:42
    +8

    Очень много вредных и неактуальных советов. Например:


    • Для анимации в старых IE лучше взять GSAP, так как он намного быстрее стандартных реализаций анимаций в jquery, и умеет с ним интегрироваться.
    • Картинки в base64 не кэшируются, да ещё и увеличивают вес (а значит и скорость парсинга) html. Плюс не стоит забывать, что если картинка грузится отдельным запросом, она не блочит загрузку всей страницы, а вот инлайн блочит. В итоге, крайне велика вероятность увеличения времени загрузки (см. http://stackoverflow.com/a/23779501/3288260 ). Так что использовать это стоит только для маленьких изображений, с умом и осторожностью.
    • Создавать элементы путём конкатенации строк, особенно без encode/sanitize, крайне опасно:
      let ajaxResult = ['1</ul>', '2']
      let div = document.createElement('div');
      div.innerHTML = '<ul>' + ajaxResult.map(e => '<li>' + e + '</li>').join('') + '</ul>';
      document.body.appendChild(div);


  1. VasiliyIsaichkin
    01.12.2016 14:42
    +11

    Мне одному кажется что этот текст опоздал лет на 5? Особенно усиливается ощущение при чтении части 6


    1. SECL
      01.12.2016 15:29
      -5

      В том, что нужно бороться с осликом 8? Чем больше проект, тем больше браузеров приходится поддерживать. Это ж очевидно. Маленьким неоправданно тратиться на такие вещи. Мы и сами в большинстве проектов не поддерживаем ИЕ 8, но несколько крупных клиентов требуют это делать все равно.


      1. Odrin
        01.12.2016 15:42
        +1

        translate3d (0, 0, 0) ускоряет анимацию

        Не надо давать таких советов. Это было актуально 3 года назад, но точно не сейчас. Бесполезное захламление кода.


        1. SECL
          01.12.2016 16:18
          -1

          Данное свойство показано как один из примеров. Да, ему несколько лет, но его никто не отменял и оно активно используется, как и множество других свойств CSS анимации.


  1. Tanner
    01.12.2016 16:22
    +4

    DocumentFragement является так званым «мини-родителем»
    API-интерфейсы по типу Web хранилищ стали огромным усилением и упрощением работы
    XMLHttpRequest (объект, который разрешен для AJAX)
    Проанализируйте свои сайты на предмет материала, который не является динамичным

    На каком языке это вообще написано? Я обычно сообщаю авторам в ЛС об ошибках и опечатках, но это просто пирдуха.


  1. Cryvage
    01.12.2016 17:17

    По аккуратней с innerHTML. Лично я бы вообще воздержался от его использования. С давних пор следую правилу: в пределах одного проекта, мы работаем с HTML елементами либо как с текстом, либо как с объектами. Но ни в коем случае не смешивая оба подхода. А поскольку в серьезном проекте одним только первым способом не обойтись, то он, как бы, отпадает.
    Вот пример возможных проблем:

    Код
    <!DOCTYPE html>
    <html>
    <head>
        <title>Test</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <script type="text/javascript" src = "jquery.js"></script>
    </head>
    <body>
        <div id = "test_div">Test</div>
        <script type="text/javascript">
            $("#test_div").on("click",function(){
                $("body")[0].innerHTML += "<div>One more test</div>";//сработает только первый раз
            });
            $("#test_div").attr("onclick","$('body')[0].innerHTML += '<div>And more</div>';"); //продолжит работать, но вряд ли вы захотите работать с событиями, как с аттрибутами
        </script>
    </body>
    </html>
    

    Подсказка
    Всй дело в «innerHTML +=»


    1. andreymal
      01.12.2016 20:44

      В innerHTML скрипты блокируются. Впрочем, другие способы напакостить всё равно остаются


      1. Cryvage
        02.12.2016 10:06

        Не знал, что они блокируются. В теории всегда думал что работает, а на практике как-то не приходилось таким заниматься. Век живи, век учись. Впрочем, пропихнуть абсолютно любой скрипт через innerHTML всё равно можно.

        Например так
        document.body.innerHTML += "<img style='display:none;visibility:hidden' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAMSURBVBhXY/j//z8ABf4C/qc1gYQAAAAASUVORK5CYII=' onload='var s=document.createElement(\"script\");s.src=\"something_very_bad.js\";this.parentNode.appendChild(s);this.parentNode.removeChild(this);' />";
        

        В принципе, тут и вставка тега script уже не нужна, можно всё написать прямо в onload. Но это не очень удобно.


  1. DenimTornado
    01.12.2016 23:21
    +1

    Вы хоть напишите, что это кривой перевод, вас так не будут тюкать, хотя…

    Особенно мне вот это понравилось: // Загружает a URL (через an AJAX запрос)

    Вот источник — http://www.cnblogs.com/yexiaochai/articles/3174750.html
    posted on 2013-07-06 01:37


    1. RubaXa
      01.12.2016 23:31
      +1

      Ба, да это же, Жан-Жак Свежак!


    1. SECL
      02.12.2016 01:23
      -1

      В начале поста я об этом писал, второй абзац.


      1. DenimTornado
        02.12.2016 09:35

        Приёмы и копипаста — разные вещи.


  1. A3a
    02.12.2016 01:43

    Вот тут советов побольше, а на русском есть перевод и на хабре.


  1. barney
    02.12.2016 15:38

    Через css медиа запросы фоном подгружать разные картинки на разных разрешениях: повернул телефон в руке и ждем пока подгрузятся новые картинки. Сомнительный совет.