Сразу должен сказать, что часть приемов было подсмотрено нами на западных просторах интернетов, а часть добавили мы. В любом случае, в рунете такого материала не было. Нам часто приходится писать большие системы и оптимизировать скорость загрузки, поэтому стараемся бороться за каждый байт. Отсюда и решили написать про эту важную тему.
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)
VasiliyIsaichkin
01.12.2016 14:42+11Мне одному кажется что этот текст опоздал лет на 5? Особенно усиливается ощущение при чтении части 6
SECL
01.12.2016 15:29-5В том, что нужно бороться с осликом 8? Чем больше проект, тем больше браузеров приходится поддерживать. Это ж очевидно. Маленьким неоправданно тратиться на такие вещи. Мы и сами в большинстве проектов не поддерживаем ИЕ 8, но несколько крупных клиентов требуют это делать все равно.
Odrin
01.12.2016 15:42+1translate3d (0, 0, 0) ускоряет анимацию
Не надо давать таких советов. Это было актуально 3 года назад, но точно не сейчас. Бесполезное захламление кода.SECL
01.12.2016 16:18-1Данное свойство показано как один из примеров. Да, ему несколько лет, но его никто не отменял и оно активно используется, как и множество других свойств CSS анимации.
Tanner
01.12.2016 16:22+4DocumentFragement является так званым «мини-родителем»
API-интерфейсы по типу Web хранилищ стали огромным усилением и упрощением работы
XMLHttpRequest (объект, который разрешен для AJAX)
Проанализируйте свои сайты на предмет материала, который не является динамичным
На каком языке это вообще написано? Я обычно сообщаю авторам в ЛС об ошибках и опечатках, но это просто пирдуха.
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 +=»andreymal
01.12.2016 20:44В innerHTML скрипты блокируются. Впрочем, другие способы напакостить всё равно остаются
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. Но это не очень удобно.
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
barney
02.12.2016 15:38Через css медиа запросы фоном подгружать разные картинки на разных разрешениях: повернул телефон в руке и ждем пока подгрузятся новые картинки. Сомнительный совет.
bromzh
Очень много вредных и неактуальных советов. Например: