10+ полезных jQuery сниппетов на каждый день

jquery

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


Плавный скролл к верху страницы

Давайте начнем этот список с очень популярного и полезного сниппета: эти 4 строки кода позволят вашим посетителям плавно проскролить страницу к верху простым нажатием ссылки (с id #top) расположенной внизу страницы.

$("a[href='#top']").click(function() {
  $("html, body").animate({ scrollTop: 0 }, "slow");
  return false;
});

Источник

Дублирование thead в самый низ html таблицы

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

var $tfoot = $('<tfoot></tfoot>');
$($('thead').clone(true, true).children().get().reverse()).each(function() {
  $tfoot.append($(this));
});
$tfoot.insertAfter('table thead');

Источник

Загрузка внешнего контента

Вам нужно добавить определенный внешний контент в div? Так вот это очень просто сделать с jQuery, как показано в нижеприведенном примере.

$("#content").load("somefile.html", function(response, status, xhr) {
  // error handling
  if(status == "error") {
    $("#content").html("An error occured: " + xhr.status + " " + xhr.statusText);
  }
});

Источник

Колонки одинаковой высоты

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

var maxheight = 0;
$("div.col").each(function() {
  if($(this).height() > maxheight) { maxheight = $(this).height(); }
});

$("div.col").height(maxheight);

Источник

Табличные полосы (зебра)

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

$(document).ready(function(){                             
     $("table tr:even").addClass('stripe');
});

Источник

Частичное обновление страницы

Если вам нужно обновить только часть страницы, то эти 3 строки кода точно помогут. В примере div с id #refresh автоматически обновляется каждые 10 секунд.

setInterval(function() {
  $("#refresh").load(location.href+" #refresh>*","");
}, 10000); // milliseconds to wait

Источник

Предзагрузка изображений

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

$.preloadImages = function() {
       for(var i = 0; i<arguments.length; i++) {
               $("<img />").attr("src", arguments[i]);
       }
}

$(document).ready(function() {
       $.preloadImages("hoverimage1.jpg","hoverimage2.jpg");
});

Источник

Открытие внешних ссылок в новом окне или новой вкладке

Аттрибут target=»_blank» позволяет вам открывать ссылки в новых окнах. Но это относится к открытию внешних ссылок, внутридоменные ссылки должны окрываться в том же окне.
Этот код находит внешнюю ссылку и добавляет в найденный элемент аттрибут target=»_blank».

$('a').each(function() {
   var a = new RegExp('/' + window.location.host + '/');
   if(!a.test(this.href)) {
       $(this).click(function(event) {
           event.preventDefault();
           event.stopPropagation();
           window.open(this.href, '_blank');
       });
   }
});

Источник

Div по ширине/высоте вьюпорта

Этот удобный фрагмент кода позволяет создавать растянутый по ширине/высоте вьюпорта div. Код также поддерживает изменение размеров окна. Прекрасное решение для модальных диалогов и popup-окон.

// global vars
var winWidth = $(window).width();
var winHeight = $(window).height();

// set initial div height / width
$('div').css({
    'width': winWidth,
'height': winHeight,
});

// make sure div stays full width/height on resize
$(window).resize(function(){
    $('div').css({
    'width': winWidth,
    'height': winHeight,
});
});

Источник

Проверка сложности пароля

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

Для начала создадим поля ввода:

<input type="password" name="pass" id="pass" />
<span id="passstrength"></span>

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

$('#pass').keyup(function(e) {
     var strongRegex = new RegExp("^(?=.{8,})(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*\\W).*$", "g");
     var mediumRegex = new RegExp("^(?=.{7,})(((?=.*[A-Z])(?=.*[a-z]))|((?=.*[A-Z])(?=.*[0-9]))|((?=.*[a-z])(?=.*[0-9]))).*$", "g");
     var enoughRegex = new RegExp("(?=.{6,}).*", "g");
     if (false == enoughRegex.test($(this).val())) {
             $('#passstrength').html('More Characters');
     } else if (strongRegex.test($(this).val())) {
             $('#passstrength').className = 'ok';
             $('#passstrength').html('Strong!');
     } else if (mediumRegex.test($(this).val())) {
             $('#passstrength').className = 'alert';
             $('#passstrength').html('Medium!');
     } else {
             $('#passstrength').className = 'error';
             $('#passstrength').html('Weak!');
     }
     return true;
});

Источник

Изменение размеров изображения

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

$(window).bind("load", function() {
	// IMAGE RESIZE
	$('#product_cat_list img').each(function() {
		var maxWidth = 120;
		var maxHeight = 120;
		var ratio = 0;
		var width = $(this).width();
		var height = $(this).height();
	
		if(width > maxWidth){
			ratio = maxWidth / width;
			$(this).css("width", maxWidth);
			$(this).css("height", height * ratio);
			height = height * ratio;
		}
		var width = $(this).width();
		var height = $(this).height();
		if(height > maxHeight){
			ratio = maxHeight / height;
			$(this).css("height", maxHeight);
			$(this).css("width", width * ratio);
			width = width * ratio;
		}
	});
	//$("#contentpage img").show();
	// IMAGE RESIZE
});

Источник

Автоматическая загрузка контента по скроллу

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

var loading = false;
$(window).scroll(function(){
	if((($(window).scrollTop()+$(window).height())+250)>=$(document).height()){
		if(loading == false){
			loading = true;
			$('#loadingbar').css("display","block");
			$.get("load.php?start="+$('#loaded_max').val(), function(loaded){
				$('body').append(loaded);
				$('#loaded_max').val(parseInt($('#loaded_max').val())+50);
				$('#loadingbar').css("display","none");
				loading = false;
			});
		}
	}
});

$(document).ready(function() {
	$('#loaded_max').val(50);
});

Источник

Проверить, загрузилось ли изображение

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

var imgsrc = 'img/image1.png';
$('<img/>').load(function () {
    alert('image loaded');
}).error(function () {
    alert('error loading image');
}).attr('src', imgsrc);

Источник

Сортировка списка в алфавитном порядке

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

$(function() {
    $.fn.sortList = function() {
    var mylist = $(this);
    var listitems = $('li', mylist).get();
    listitems.sort(function(a, b) {
        var compA = $(a).text().toUpperCase();
        var compB = $(b).text().toUpperCase();
        return (compA < compB) ? -1 : 1;
    });
    $.each(listitems, function(i, itm) {
        mylist.append(itm);
    });
   }

    $("ul#demoOne").sortList();

});

Источник

Как копировать текст в буфер обмена с помощью JavaScript

copy text clipboard javascript

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

В этой короткой статье мы рассмотрим мою технику реализации копирования текста в буфер обмена с помощью JavaScript. Она написана на Angular. Но данный подход можно применить к любым проектам, на основе JavaScript или TypeScript.

Полная версия проекта доступна в этом репозитории GitHub. Демо-версию можно найти здесь.

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

Рассмотрим код

Сначала мы проверяем, доступен ли API Clipboard. Если да, то записываем текстовую строку в буфер обмена. Если нет, то проверяем доступность API clipboardData. Если доступен, то присваиваем clipboardData значение  текстовой строки.

При отсутствии поддержки обоих API реализуем обходные пути для Microsoft Edge, iOS и Mac OS.

copy() {
try {
if ((navigator as any).clipboard) {
(navigator as any).clipboard.writeText(this.couponCode);
} else if ((window as any).clipboardData) { // для Internet Explorer
(window as any).clipboardData.setData('text', this.couponCode);
} else { // для других браузеров, iOS, Mac OS
this.copyToClipboard(this.inputEl.nativeElement);
}
this.tooltipText = 'Copied to Clipboard.'; // копирование проведено успешно.
} catch (e) {
this.tooltipText = 'Please copy coupon manually.'; // копирование не удалось.
}
}
private copyToClipboard(el: HTMLInputElement) {
const oldContentEditable = el.contentEditable;
const oldReadOnly = el.readOnly;
try {
el.contentEditable = 'true'; //  специально для iOS
el.readOnly = false;
this.copyNodeContentsToClipboard(el);
} finally {
el.contentEditable = oldContentEditable;
el.readOnly = oldReadOnly;
}
}
private copyNodeContentsToClipboard(el: HTMLInputElement) {
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(el);
selection.removeAllRanges();
selection.addRange(range);
el.setSelectionRange(0, 999999);

Ссылка на gist

Третья и четвертая строчки кода будут работать в большинстве современных браузеров. В коде используется navigator.clipboard.writeText () – новый API для асинхронного буфера обмена, который позволяет напрямую обращаться к объекту clipboard из экземпляра navigator. Данный API в настоящее время поддерживается Chrome 66+, Firefox 63+, Opera 53+ и т.д.

Строки 5 и 6 предназначены для Internet Explorer и аналогичных браузерах. API задокументирован на странице в MSDN.

Строки 7 и 8 охватывают все остальные варианты. Если код завершается ошибкой, строки 11 и 12 обработают исключение и позволят пользователю скопировать текст вручную.

Чтобы реализовать поддержку iOS устройств, предназначен код, расположенный в строках 21 и 22. После копирования строки с 24 по 26 восстановят элемент ввода до исходного состояния.

Демо примера можно изучить здесь. Полная версия проекта доступна в этом репозитории GitHub.

Mozilla усилила защиту пользователей Firefox от вредоносного кода

mozilla защитит от вредоносного кода
image

Фото: Doug Belshaw/Flickr

Mozilla расширила защиту пользователей браузера Firefox от атак с попытками внедрения вредоносного кода. Разработчики сосредоточились на удалении «потенциально опасных артефактов» в исходном коде Firefox.

Под артефактами подразумеваются встроенные скрипты и функции типа eval(). Mozilla рассчитывает улучшить защиту встроенных служебных страниц «about:» за счет удаления таких кусков кода.

Всего насчитывается 45 страниц «about:», которые позволяют пользователям углубиться в конфигурацию браузера, а также просмотреть установленные плагины и отобразить сетевую информацию. Разработчиков беспокоила возможность провести инъекцию кода с помощью страницы about:config, позволяющей пользователям адаптировать свой Firefox к конкретным потребностям, так как страница «раскрывала API для обновления настроек и конфигурации».

Страницы «about:» написаны на HTML и JavaScript и имеют те же слабые места, что и обычные веб-страницы, то есть хакер может внедрить свой код непосредственно в служебную страницу и выполнять действия от имени пользователя. Однако Mozilla ввела защиту, которая запрещает выполнять код JavaScript, внедренный злоумышленником. Весь встроенный код JavaScript переместили в упакованные файлы для всех страниц about:. Код JavaScript выполняется только при загрузке из упакованного ресурса с использованием внутреннего протокола chrome:.

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

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

В сентябре Mozilla объявила о решении включить по умолчанию протокол DoH (DNS-over-HTTPS) в одной из следующих версий браузера Firefox. Разработанный Mozilla, Google и Cloudflare протокол DNS-шифрования сводит на нет попытки мониторинга трафика «человеком-в-середине». Он устраняет открытые DNS-запросы, по которым злоумышленник может отслеживать содержимое DNS-пакетов и даже подменять их. Это позволяет блокировать доступ к ресурсу по IP-адресу или доменному имени.

Что нового можно ожидать от Node.js в 2020 году?

что нового node js 2020

В 2019 году Node.js исполнилось 10 лет. Количество пакетов, доступных в реестре npm, пересекло отметку в 1 миллион. С каждым годом объём загрузок самой платформы Node.js увеличивается на 40%. Ещё одной важной вехой для Node.js стало то, что этот проект присоединился к OpenJS Foundation. Благодаря этому можно ожидать улучшения состояния и стабильности проекта, а также, в целом, положительных сдвигов в области взаимодействия членов JavaScript-сообщества.

Несложно заметить то, что за короткий отрезок времени, за год, в мире Node.js произошло много всего интересного. Каждый год Node.js набирает обороты. У нас нет причин рассчитывать на что-то другое в 2020.

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

Новшества Node.js 13

В начале декабря 2019 года, во время написания этого материала, Node.js 13 — это самая свежая версия данной платформы. Тут уже имеется много новых возможностей и улучшений, с которыми можно начинать экспериментировать, готовясь к наступлению нового года. Вот некоторые из них:

  • ECMAScript-модули.
  • Поддержка WebAssembly.
  • Диагностические отчёты.
  • Полная поддержка интернационализации. В частности, речь идёт о форматах даты, времени, чисел, валют.
  • Поддержка протокола QUIC.
  • Улучшение производительности JavaScript-движка V8.

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

Процесс выпуска новых версий Node.js в 2020 году

Новая мажорная версия Node.js выпускается каждые 6 месяцев. Одна — в октябре, другая — в апреле. Их называют «текущими версиями». На момент написания этого материала текущей версией Node.js является 13-я, вышедшая в октябре 2019 года.

Версии с нечётными номерами (v9, v11, v13) выпускаются каждый октябрь. Актуальны они сравнительно недолго, они не считаются готовыми для применения в продакшне. Их можно считать бета-версиями платформы. Они рассчитаны на тестирование новых возможностей и изменений, которые попадут в следующую версию Node.js с чётным номером.

Версии с чётными номерами (v8, v10, v12) выходят каждый апрель. После выпуска подобной версии прекращается выпуск обновлений для предыдущей «чётной» версии. Хотя эти версии и гораздо стабильнее «нечётных», активная работа над ними продолжается ещё 6 месяцев. В это время их можно считать находящимися в состоянии релиз-кандидата.

После того, как «чётная» версия будет доработана в течение 6 месяцев, она входит в новую стадию своего жизненного цикла, превращаясь в LTS-версию (Long-Term Support, долгосрочная поддержка). LTS-версии Node.js считаются готовыми для использования в продакшне. Следующие 12 месяцев для таких версий выпускаются исправления ошибок, обновления безопасности и другие улучшения. Делается всё это с учётом сохранения работоспособности существующих приложений.

После LTS-стадии наступает стадия сопровождения (Maintenance stage). В это время выпускаются только исправления критических ошибок и обновления безопасности. Стадия сопровождения длится 18 месяцев. После того, как это время пройдёт, соответствующая версия переходит в стадию окончания жизненного цикла (EOL, End-of-Life) и больше не поддерживается.

Жизненный цикл версий Node.js

Ожидаемый план выпуска новых версий Node.js в 2020 году

В 2020 году можно ожидать следующего плана выпуска новых версий Node.js:

Январь-март 2020

  • Текущей версией является 13.x, ведётся активная работа над ней.
  • Версии 10.x и 12.x находятся в состоянии LTS.

Апрель 2020

  • Версия 14.x становится текущей.
  • Работа над версией 13.x останавливается вскоре после выпуска 14.x.
  • Версия 10.x входит в стадию сопровождения.

Октябрь 2020

  • Выходит версия 15.x, которая становится текущей.
  • Версия 14.x входит в фазу LTS.
  • Версия 12.x переходит в стадию сопровождения.

План выпуска новых версий Node.js в 2020 году

Обратите внимание на то, что конец жизненного цикла Node.js 8 назначен на конец 2019 года. Дело в том, что эта версия платформы зависит от OpenSSL-1.0.2, а жизненный цикл этой версии OpenSSL так же оканчивается в конце 2019 года. Спланируйте перевод Node.js 8.x-приложений на Node.js 10.x или 12.x если вы ещё этого не сделали.

ECMAScript-модули

В версии 13.2.0 Node.js поддерживает и CommonJS-модули, и новые стандартные ECMAScript-модули (ES-модули) без необходимости использования каких-либо сторонних инструментов. Это означает, что разработчики наконец-то могут пользоваться инструкциями import и export, теми же самыми, которыми они, вероятно, уже пользуются при создании фронтенд-проектов на JavaScript. Кроме того, важно отметить, что ES-модули в Node.js по умолчанию работают в строгом режиме. В результате для включения этого режима не нужно добавлять в начало каждого файла "use strict";.

// файл message
async function sendMessage { ... }
export { sendMessage };

// файл index
import { sendMessage } from "./message";

Однако для того, чтобы сообщить Node.js о том, что разработчик применяет ES-модули, нужно кое-что сделать. Тут можно воспользоваться одним из двух наиболее часто применяемых способов. Первый из них заключается в том, что файлам назначают расширение .mjs. Второй — в том, что в ближайшем родительском файле package.json размещают конструкцию "type": "module".

С первым подходом всё понятно — .js-файлы переименовывают в .mjs-файлы. При использовании второго подхода в корневой файл package.json, или в package.json, помещённый в папку, содержащую ES-модули, добавляют свойство type со значением module:

{
   "type": "module"
}

Ещё одна возможность, касающаяся работы с ES-модулями, заключается в их включении в корневом файле package.json проекта и в изменении расширений файлов, использующих CommonJS-модули, на .cjs.

Лично мне кажется, что использование расширений .mjs или .cjs — это не особенно удачная идея. Поэтому мне приятно видеть то, что для работы с ES-модулями достаточно внести изменения в файл package.json.

Импорт WebAssembly-модулей

Node.js теперь поддерживает не только ES-модули, но и импорт WebAssembly-модулей (Wasm-модулей). Wasm-модуль — это файл, содержащий код в переносимом бинарном формате, который можно распарсить быстрее, чем аналогичный JavaScript-код, и выполнить со скоростью, аналогичной нативной. Wasm-модули можно создавать, разрабатывая исходный код на таких языках, как C/C++, Go, C#, Java, Python, Elixir, Rust, да и на многих других.

Поддержка WebAssembly-модулей всё ещё находится в экспериментальной стадии. Для того чтобы включить эту возможность, нужно передать Node.js специальный флаг командной строки при запуске приложения:

node --experimental-wasm-modules index.js

Рассмотрим пример. Предположим, у нас имеется библиотека для обработки изображений, реализованная в виде Wasm-модуля. Для работы с такой библиотекой в JS-коде можно поступить так:

import * as imageUtils from "./imageUtils.wasm";
import * as fs from "fs";
( async () => {
   const image = await fs.promises.readFile( "./image.png" );
   const updatedImage = await imageUtils.rotate90degrees( image );
} )();

Тут мы импортировали модуль и воспользовались имеющейся в нём функцией.

Wasm-модули в Node.js можно импортировать и пользуясь новой динамической инструкцией import():

"use strict";
const fs = require("fs");
( async () => {
   const imageUtils = await import( "./imageUtils.wasm" );
   const image = await fs.promises.readFile( "./image.png" );
   const updatedImage = await imageUtils.rotate90degrees( image );
} )();

Системный интерфейс WebAssembly

Технология WebAssembly, как и JavaScript, была разработана с учётом требований безопасности. Wasm-код выполняется в защищённом окружении, которое иногда называют «песочницей». Это позволяет защитить от такого кода операционную систему, в которой он работает. Однако в некоторых случаях Wasm-модули, работающие в среде Node.js, могут только выиграть от возможности выполнения системных вызовов.

Здесь на сцену и выходит системный интерфейс WebAssembly (WebAssembly System Interface, WASI). WASI спроектирован как стандартный интерфейс для выполнения обращений к системам, на которых выполняется Wasm-код. В роли таких систем могут выступать хост-приложения, операционные системы, и так далее.

В репозитории Node.js можно обнаружить недавний коммит, вводящий первоначальную поддержку WASI. Собственно говоря, системный интерфейс WebAssembly — это одна из интересных возможностей Node.js, появления которой можно ожидать в 2020 году.

Поддержка диагностических отчётов

Диагностические отчёты — это JSON-документы, предназначенные для людей, содержащие сведения о работе программных механизмов. Среди таких сведений могут быть стеки вызовов функций, информация об операционной системе, данные о загруженных модулях и другие полезные показатели, направленные на помощь в поддержке приложений. Подобные отчёты можно создавать при возникновении необработанных исключений, критических ошибок. Их можно формировать по сигналам от процессов или с использованием нового API process.report. Node.js можно настроить так, чтобы диагностические отчёты сохранялись бы в заданной папке с использованием заданных имён файлов.

Сейчас эта возможность носит статус экспериментальной. Для её включения нужно передать Node.js специальный флаг при запуске приложения:

node --experimental-report --report-uncaught-exception --report-filename=./diagnostics.json index.js

Расширение поддержки интернационализации

В Node.js 13.x включена полная версия библиотеки ICU (International Components for Unicode). ICU — это зрелый популярный проект. Среди множества возможностей этой библиотеки можно отметить поддержку форматирования чисел, дат, валют, вывод времени в локализованном формате. Она умеет выполнять вычисления, связанные с временными промежутками, умеет сравнивать строки и выполнять перекодировку текстов из Unicode в другие кодировки и обратно.

Некоторые другие новые возможности Node.js

Вот ещё некоторые интересные возможности Node.js, появления которых можно ожидать в 2020 году:

  • Поддержка протокола QUIC. QUIC — это современный интернет-протокол, с помощью которого можно налаживать надёжные и производительные связи между приложениями.
  • Улучшенная поддержка Python 3. В 2020 году у нас должна появиться возможность сборки Node.js-модулей и нативных модулей с использованием Python 3.
  • Обновлённая версия JS-движка V8. Версии 7.8 и 7.9 движка V8 дадут увеличение производительности приложений и поддержку Wasm.
  • Стабильный API Worker Threads. API Worker Threads позволяет распараллеливать выполнение интенсивных вычислений.

Кэширование кода для JavaScript-разработчиков на примере Chrome

кэширование-js

Кэширование кода (также называемое кэшированием байт-кода) является важным инструментом оптимизации. Оно уменьшает время запуска часто посещаемых сайтов за счёт кэширования результатов парсинга и компиляции. Большинство популярных браузеров реализует кэширование в некоторой форме, и Chrome не исключение. О том, как Chrome и V8 кэшируют скомпилированный код, уже много всего написано и сказано.

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

Обзор кэширования кода

Кэш оперативной памяти

У Chrome есть два уровня кэширования скомпилированного в V8 кода (классических скриптов и скриптов модулей): быстрый и «лучший из возможного» кэш в оперативной памяти, обеспечиваемый средствами V8 (кэш Isolate), а также полный сериализованный кэш на диске.

Кэш Isolate работает со скриптами, скомпилированными в том же V8 Isolate (т. е. тот же процесс, грубо говоря «одни и те же страницы сайта при навигации по одной и той же вкладке»). Это «лучшее из возможного» в том смысле, что кэш оперативной памяти, насколько это возможно, быстр и минимален: он использует уже имеющиеся данные за счёт потенциально более низкой частоты обращений и отсутствия кэширования между процессами.

  • Когда V8 компилирует скрипт, скомпилированный байт-код сохраняется в хеш-таблице (в куче V8), ключ которого определяется исходным кодом сценария.
  • Когда Chrome просит V8 скомпилировать другой скрипт, V8 сначала проверяет, соответствует ли исходный код этого скрипта чему-либо в хеш-таблице. Если соответствует, просто возвращается существующий байт-код.

Этот кэш быстрый и эффективный, но на практике у него лишь 80% частоты попаданий.

Кэш на диске

Кэш кода на диске управляется Chrome (в частности, с помощью Blink) и заполняет пробел, который кэш Isolate не может заполнить: совместное использование кэшей кода между процессами и между несколькими сеансами Chrome. Он использует преимущества существующего кэша HTTP-ресурсов, который управляет кэшированием и очисткой данных с истёкшим сроком действия, полученных по сети.

  1. Когда JS-файл запрашивается впервые (т. е. выполняется «холодный» запуск), Chrome загружает его и даёт V8 для компиляции. Он также сохраняет файл в кэше браузера на диске.
  2. Когда JS-файл запрашивается во второй раз (т. е. выполняется «тёплый» запуск), Chrome берёт файл из кэша браузера и снова передаёт его в V8 для компиляции. Однако на этот раз скомпилированный код сериализуется и прикрепляется к кэшированному файлу скрипта в качестве метаданных.
  3. В третий раз (т. е. «горячий» запуск) Chrome извлекает как файл, так и метаданные файла из кэша и передаёт их в V8. Тот в свою очередь десериализует метаданные и может пропустить компиляцию.

В итоге:

cache

Кэширование кода делится на холодные, тёплые и горячие запуски, используя кэш в оперативной памяти при тёплых запусках и дисковый кэш при горячих запусках.

Основываясь на этом описании, можно оптимизировать использование кэшей кода на вашем сайте.

Совет 1: не делайте ничего

Лучшее, что JS-разработчик может сделать для оптимизации кэширования кода, — ничего не делать. Но ничего не делать можно по-разному: пассивно и активно.JSSP Meetup #412 декабря в 18:45, Сергиев Посад, беcплатноtproger.ruСобытия и курсы на tproger.ru

Кэширование кода в конце концов является частью реализации браузера. По сути, это увеличение производительности за счёт дополнительных расходов памяти, реализация и эвристика которых могут постоянно меняться. Мы, как разработчики V8, должны делать всё возможное, чтобы эти эвристики работали для каждого. Если чрезмерно оптимизировать кэширование кода, можно очень разочароваться уже после нескольких релизов, когда эти детали изменятся. Кроме того, другие механизмы JavaScript могут иметь различные эвристики для своей реализации кэширования кода. Так что во многих отношениях лучший совет для получения кэшированного кода похож на совет по написанию JS: пишите чистый идиоматический код и сделайте всё возможное, чтобы оптимизировать его кэширование.

Помимо пассивного бездействия, вы также должны стараться активно «ничего не делать». Любая форма кэширования по своей природе зависит от того, что ничего не меняется, поэтому бездействие — лучший способ сохранить кэшированные данные. Активно ничего не делать можно разными способами.

Не меняйте код

Всякий раз, когда вы отправляете новый код, он ещё не кэширован. Когда браузер делает HTTP-запрос для URL-адреса сценария, он может включать дату последней выборки этого URL-адреса. Если сервер знает, что файл не изменился, он может отправить ответ «304 Not Modified», который сохраняет кэш кода «горячим». В противном случае ответ «200 OK» обновляет кэшированный ресурс и очищает кэш кода, возвращая его обратно в «холодный» режим.

Всегда хочется сразу же отправить последние изменения кода, особенно если вы хотите измерить влияние конкретного изменения, но для кэша лучше позволить коду существовать как он есть, по крайней мере, обновлять его как можно реже. Подумайте о том, чтобы установить ограничение «≤ Х развёртываний в неделю», где Х — это слайдер, который вы можете регулировать для баланса между кэшированием и устареванием данных.

Не меняйте URL’ы

Кэш кода связан с URL-адресом скрипта, так как это облегчает поиск, ведь нет необходимости читать фактическое содержимое скрипта. Это означает, что изменение URL-адреса (включая любые параметры запроса) создаёт новую запись в кэше ресурсов, а вместе с ним и новую запись «холодного» кэша.

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

Не меняйте поведение выполнения

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

Эта оптимизация работает лучше всего, когда каждый запуск скрипта выполняет один и тот же код или хотя бы одинаковые функции. Это может быть проблемой, если у вас есть, например A/B-тесты, которые зависят от выбора времени выполнения:

if (Math.random() > 0.5) {
  A();
} else {
  B();
}

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

Совет 2: сделайте что-нибудь

Конечно, совет не делать ничего (пассивно или активно) не очень удовлетворит вас. Помимо того что вы ничего не делаете, учитывая текущую эвристику и реализацию, есть несколько вещей, которые вы можете сделать. Эвристика может измениться, сам этот совет может измениться и нет никакой альтернативы для профилирования.

Отделите библиотеки от кода

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

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

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

Объедините библиотеки с кодом

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

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

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

Одним из решений этой проблемы является объединение библиотек в единый сценарий, таким образом кэширование кода «видит», какие части библиотеки используются. Это полная противоположность совету выше, универсального решения нет. Конечно, не рекомендуется объединять все ваши JS-скрипты в один большой бандл. В целом разделение его на несколько более мелких скриптов будет полезнее (например, множественные сетевые запросы, потоковая компиляция, интерактивность страниц и т. д.).

Используйте преимущество эвристики IIFE

Только те функции, которые скомпилированы к моменту, когда завершится выполнение скрипта, учитываются в кэше, поэтому существует много видов функций, которые не будут кэшироваться, несмотря на выполнение в более поздний момент. Обработчики событий (даже onload()), цепочки промисов, неиспользуемые библиотечные функции и всё, что лениво компилируется без вызова к моменту, когда </script> виден — всё это остаётся ленивым и не кэшируется.

Один из способов сделать эти функции кэшированными — заставить их компилироваться. Распространённым способом принудительной компиляции является использование эвристики IIFE. IIFE (immediately-invoked function expressions) — это шаблон, в котором функция вызывается сразу после создания:

(function foo() {
  // …
})();

Так как IIFE вызываются немедленно, большинство движков JavaScript пытаются обнаружить их и немедленно скомпилировать, чтобы избежать затрат на ленивую компиляцию с последующей полной компиляцией. Существуют различные эвристики для раннего обнаружения IIFE (до анализа функции), наиболее распространённой из которых является символ «(» перед ключевым словом function.

Поскольку эта эвристика применяется рано, она запускает компиляцию, даже если функция на самом деле вызывается не сразу:

const foo = function() {
  // Лениво пропущено
};
const bar = (function() {
  // С нетерпением скомпилировано
});

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

Группируйте небольшие файлы вместе

В Chrome — минимальный размер для кэшей кода, сейчас это 1 КБ исходного кода. Сценарии меньше не кэшируются, так как затраты будут больше выгоды.

Если на вашем сайте много таких небольших сценариев, подсчёт затрат может больше не применяться аналогичным образом. Лучше объединить их так, чтобы они превышали минимальный размер кэширования кода, и получить выгоду от общего сокращения затрат скрипта.

Избегайте встроенных скриптов

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

Простые сценарии не стоит встраивать в HTML, лучше выносить их в отдельные файлы.

Используйте кэши сервис-воркера

Сервис-воркер — это механизм, позволяющий вашему коду перехватывать сетевые запросы на ресурсы на вашей странице. Они позволяют вам создавать локальный кэш из некоторых ваших ресурсов и обслуживать ресурс из кэша всякий раз, когда их запрашивают. Это особенно полезно для страниц, которые продолжают работать в offline-режиме вроде PWA.

Ниже типичный пример сайта, использующего сервисный воркер. Регистрация воркера в основном файле сценария:

// main.mjs
navigator.serviceWorker.register('/sw.js');

Воркер добавляет обработчики событий для установки (создание кэша) и извлечения (обслуживание ресурсов).

// sw.js
self.addEventListener('install', (event) => {
  async function buildCache() {
    const cache = await caches.open(cacheName);
    return cache.addAll([
      '/main.css',
      '/main.mjs',
      '/offline.html',
    ]);
  }
  event.waitUntil(buildCache());
});

self.addEventListener('fetch', (event) => {
  async function cachedFetch(event) {
    const cache = await caches.open(cacheName);
    let response = await cache.match(event.request);
    if (response) return response;
    response = await fetch(event.request);
    cache.put(event.request, response.clone());
    return response;
  }
  event.respondWith(cachedFetch(event));
});

Эти кэши могут включать в себя кэшированные ресурсы JS. Но поскольку ожидается, что кэши воркеров будут преимущественно использоваться для PWA, для них используется немного другая эвристика по сравнению с обычным «автоматическим» кэшированием в Chrome. Во-первых, они сразу же создают кэш кода при каждом добавлении ресурса JS. Это означает, что кэш доступен уже при второй загрузке (а не только при третьей, как в обычном случае). Во-вторых, генерируется «полный» кэш для этих скриптов — функции больше не компилируются лениво. Всё компилируется и помещается в кэш. Преимущество заключается в быстрой и предсказуемой производительности, без каких-либо зависимостей порядка выполнения, хоть и за счёт увеличения использования памяти. Обратите внимание, что такая эвристика применяется только к кэшам сервисных воркеров, а не к другому использованию Cache API. В настоящее время Cache API вообще не выполняет кэширование кода, когда используется вне сервисных воркеров.

Трассировка

Ни один из советов выше не поможет ускорить работу вашего сайта. К сожалению, информация о кэшировании сейчас не предоставляется в DevTools, поэтому наиболее надёжный способ выяснить, какие из сценариев вашего сайта кэшируются, — использовать чуть более низкий уровень chrome://tracing.

chrome://tracing записывает инструментальные трассировки Chrome в течение некоторого периода времени с такой визуализацией :

tracing

Трассировка записывает поведение всего браузера, включая другие вкладки, окна и расширения, поэтому она лучше всего работает, когда выполняется в чистом профиле пользователя, с отключёнными расширениями и без открытия других вкладок браузера:

# Запустите новый сеанс браузера Chrome с чистым профилем пользователя и отключёнными расширениями
google-chrome --user-data-dir="$(mktemp -d)" --disable-extensions

При записи вы должны выбрать, какие категории трассировать. В большинстве случаев вы можете просто выбрать набор категорий «Web developer» (Веб-разработчик), но категории можно выбрать и вручную. Важная категория для кэширования кода — v8.

chrome tracing categories web developer
chrome tracing categories v8

После записи с категорией v8 найдите фрагменты v8.compile в трассировке (в качестве альтернативы вы можете ввести v8.compile в поле поиска интерфейса). Эти компоненты показывают компилируемый файл и некоторые метаданные о компиляции.

При холодном запуске сценария информация о кэшировании кода отсутствует. Это означает, что сценарий не участвовал в создании или использовании данных кэша.

chrome tracing cold run

При тёплом запуске есть две записи v8.compile на сценарий: одна для фактической компиляции (как указано выше) и одна (после выполнения) для создания кэша. Вы можете узнать последнюю, так как она имеет поля метаданных cacheProduceOptions и producedCacheSize.

chrome tracing warm run

При горячем запуске вы увидите запись v8.compile для использования кэша с полями метаданных cacheConsumeOptions и consumedCacheSize. Все размеры выражены в байтах.

chrome tracing hot run

Для большинства разработчиков кэширование кода должно «просто работать». И работает это лучше всего (как и любой кэш), когда всё остаётся неизменным, и с использованием эвристики, которая может меняться между версиями. Тем не менее, кэширование кода имеет особенности, которые можно использовать, и ограничения, которых стоит избегать. Тщательный анализ с использованием chrome://tracing может помочь вам настроить и оптимизировать использование кэша вашим сайтом.