От установления http-соединения до отображения страницы в браузере она прошла несколько этапов загрузки, выполнения, рендеринга и рефакторинга. Поделюсь своим опытом и отличным опытом других.
загрузить и выполнить
Браузер является дружественным клиентом, и существует ограничение на количество одновременных запросов для одного и того же доменного имени.В прошлом количество браузеров, как правило, было 2, а количество поддерживаемых H5, как правило, 6; серверная сторона может закрыть запрос. Некоторые друзья не понимают, почему чем больше параллелизма, тем лучше? Например: каковы последствия миллионного PV и чрезмерного количества одновременных событий? Исходя из этого, все оптимизации расширены на основе этой точки и однопоточности. Поэтому оптимизация загрузки ресурсов фронтенда имеет два направления
- Открытый исходный код Добавить доменные имена Так как одного и того же доменного имени не может быть слишком много, то доменных имен больше, короче это cdn, который может быть сторонним, либо вы можете получить еще несколько доменных имен второго уровня самостоятельно
- дросселирование
Сжатие ресурсов и загрузка по требованию файлов в одном и том же доменном имени полностью сжаты. Например, если исходный ресурс размером 2 МБ сжат до менее чем 1 МБ (удаление пробелов, gzip и т. д.), скорость будет увеличена на 50 %. ; и теперь spa для объединения файлов. После сжатия и упаковки, если общий файл невелик, производительность не будет иметь большого влияния; как только в разработку будут добавлены дополнительные библиотеки пользовательского интерфейса или сторонние плагины, общий файл объем не маленький, есть: загрузка по требованию, где заходит отложенная загрузка. Например, при упаковке webpack отдельно от html шаблона добавляется css или js, также есть библиотека webpack-http-require.
Конечно, фотографии также нуждаются в соответствующей обработке.
- css для достижения эффектов (кнопки, тени и т.д.)
- Сжатый размер и размер
- спрайт слияние
- svg, карта шрифтов toff
- На холсте нарисуйте большую картинку (связанную с картой)
Блокировка оптимизации
Вы хотите выполнить сразу после загрузки файла js? Влияет ли его выполнение на рендеринг страницы? Раньше браузеры находились в состоянии блокировки при загрузке и выполнении js-файлов один за другим по принципу стека, поэтому изначально требовалось ставить js-файлы перед низом html-кода.Современные браузеры решают проблему параллельная загрузка в некоторой степени, предварительная загрузка также возможна, но будет ли страница перекомпоновываться после выполнения? Поэтому необходимо гибко применять dns-prefetch, preload и defer|async.Конечно, defer и async действуют не во всех браузерах, а ядро webkit не действует.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Demo</title>
<link rel="dns-prefetch" href="//cdn.com/">
<link rel="preload" href="//js.cdn.com/currentPage-part1.js" as="script">
<link rel="preload" href="//js.cdn.com/currentPage-part2.js" as="script">
<link rel="preload" href="//js.cdn.com/currentPage-part3.js" as="script">
<link rel="prefetch" href="//js.cdn.com/prefetch.js">
</head>
<body>
<!-- html code -->
<script type="text/javascript" src="//js.cdn.com/currentPage-part1.js" defer></script>
<script type="text/javascript" src="//js.cdn.com/currentPage-part2.js" defer></script>
<script type="text/javascript" src="//js.cdn.com/currentPage-part3.js" defer></script>
</body>
</html>
js выполняет оптимизацию
- Оптимизация области видимости, уровень переменных не должен быть слишком глубоким или слишком сильно вложенным, предпочтительно этот уровень; когда вы смотрите на основные фреймворки или библиотеки, вы часто можете увидеть такой способ написания:
(function(w,d){})(window,document)
// 目的就是如此,再比如说的缓存某个变量或对象
function check(){
var d = document, t = document.getElementById('t'), l = t.children;
for(let i=0;i<l;i++){
//code
}
}
- Оптимизация цикла Циклы являются наиболее распространенными конструкциями в программировании, и оптимизация циклов является важной частью процесса оптимизации производительности. Основные этапы оптимизации цикла следующие:
Уменьшение итерации. В большинстве циклов используется итератор, который начинается с 0 и увеличивается до определенного значения. Во многих случаях итераторы, уменьшающие значение в цикле, более эффективны, начиная с максимального значения. Упростите условие завершения. Поскольку условие завершения вычисляется каждый раз при прохождении цикла, оно должно выполняться как можно быстрее, т. е. избегать поиска атрибутов или других операций O (n). Упростите тело цикла. Тело цикла выполняется больше всего, поэтому убедитесь, что оно максимально оптимизировано. Убедитесь, что нет интенсивных вычислений, которые можно легко вывести из цикла. Используйте посттестовые циклы — наиболее часто используемые циклы for и while являются циклами предварительного тестирования, в то время как такие циклы, как do-while, могут избежать оценки начального условия завершения, что происходит быстрее.
for(var i = 0; i < values.length; i++) {
process(values[i]);
}
Оптимизация 1: упростить условие завершения
for(var i = 0, len = values.length; i < len; i++) {
process(values[i]);
}
Оптимизация 2: используйте цикл после тестирования (примечание: цикл тестирования после использования должен гарантировать, что хотя бы одно из значений будет обработано)
var i values.length - 1;
if(i > -1) {
do {
process(values[i]);
}while(--i >= 0);
}
- Развернуть петлю
Когда количество циклов определено, часто бывает быстрее устранить цикл и использовать несколько вызовов функций. Когда количество циклов неизвестно, для оптимизации можно использовать службу Duff.Основная концепция состоит в том, чтобы развернуть цикл в серию операторов, вычислив, является ли количество итераций кратным 8. следующим образом:
// Jeff Greenberg for JS implementation of Duff's Device
// 假设:values.length > 0
function process(v) {
alert(v);
}
var values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17];
var iterations = Math.ceil(values.length / 8);
var startAt = values.length % 8;
var i = 0;
do {
switch(startAt) {
case 0 : process(values[i++]);
case 7 : process(values[i++]);
case 6 : process(values[i++]);
case 5 : process(values[i++]);
case 4 : process(values[i++]);
case 3 : process(values[i++]);
case 2 : process(values[i++]);
case 1 : process(values[i++]);
}
startAt = 0;
}while(--iterations > 0);
Развертывание цикла, как описано выше, может ускорить обработку больших наборов данных. Далее дается более быстрая техника устройства Даффа, в которой цикл do-while разбивается на 2 отдельных цикла. (Примечание: этот метод почти на 40% быстрее, чем исходная реализация устройства Duff.)
// Speed Up Your Site(New Riders, 2003)
function process(v) {
alert(v);
}
var values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17];
var iterations = Math.floor(values.length / 8);
var leftover = values.length % 8;
var i = 0;
if(leftover > 0) {
do {
process(values[i++]);
}while(--leftover > 0);
}
do {
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
}while(--iterations > 0);
Использование развернутых циклов для больших наборов данных может сэкономить много времени, но для небольших наборов данных дополнительные накладные расходы могут быть того не стоит.
- избегать двойного толкования
При парсинге JS тег кода JS захочет объяснить наличие двойного штрафа при использовании функции eval() или использовании конструктора Function, а setTimeout() будет передавать строку случиться. следующим образом
eval("alert('hello world');"); // 避免
var sayHi = new Function("alert('hello world');"); // 避免
setTimeout("alert('hello world');", 100);// 避免
Приведенный выше код содержится в строке, то есть должен быть запущен новый парсер для разбора нового кода во время выполнения JS-кода. Создание нового синтаксического анализатора сопряжено с существенными накладными расходами, поэтому этот код работает медленнее, чем прямой синтаксический анализ. Следует по возможности избегать следующих примеров, за исключением редких случаев, когда требуется eval. Для конструктора Function его можно напрямую записать как общую функцию. Для setTimeout вы можете передать функцию в качестве первого параметра. следующим образом:
alert('hello world');
var sayHi = function() {
alert('hello world');
};
setTimeout(function() {
alert('hello world');
}, 100);
Короче говоря, чтобы повысить производительность кода, старайтесь избегать кода, который необходимо интерпретировать в соответствии с JS.
-
Дополнительные соображения по производительности Нативные методы работают быстрее — по возможности используйте нативные методы, а не переписывайте их на JS. Нативные методы написаны на компилируемых языках, таких как C/C++, которые намного быстрее, чем JS. Многие думают, что пользовательская сортировка будет быстрее, чем sortby, и дело в том, что эффект сравнения лучше, чем нативный метод. Операторы switch быстрее — если у вас есть серия сложных операторов if-else, вы можете получить более быстрый код, преобразовав их в один оператор switch.Вы также можете получить более быстрый код, организовав операторы case в порядке от наиболее вероятного к наименее вероятному. , дальнейшая оптимизация. Побитовые операции выполняются быстрее — при выполнении математических операций побитовые операции выполняются быстрее, чем любые логические или арифметические операции. При необходимости замена арифметических операций битовыми операциями может значительно повысить производительность сложных вычислений, например операций по модулю, логического И и логического ИЛИ. Также рассмотрите возможность замены их битовыми операциями.
-
Минимизируйте количество утверждений Количество операторов в коде JS также влияет на скорость выполняемых операций: один оператор, выполняющий несколько операций, быстрее, чем несколько блоков операторов, выполняющих одну операцию. Поэтому необходимо выяснить операторы, которые можно объединить вместе, чтобы сократить общее время выполнения. Вот несколько режимов
1. Несколько объявлений переменных
// 避免
var i = 1;
var j = "hello";
var arr = [1,2,3];
var now = new Date();
// 提倡
var i = 1,
j = "hello",
arr = [1,2,3],
now = new Date();
2. Вставьте значение итерации
// 避免
var name = values[i];
i++;
// 提倡
var name = values[i++];
3. Используйте массивы и литералы объектов, избегайте использования конструкторов Array(), Object()
// 避免
var a = new Array();
a[0] = 1;
a[1] = "hello";
a[2] = 45;
var o = new Obejct();
o.name = "bill";
o.age = 13;
// 提倡
var a = [1, "hello", 45];
var o = {
name : "bill",
age : 13
};
4. Оптимизируйте взаимодействие с DOM В JS DOM, несомненно, самая медленная часть, манипулирование и взаимодействие с DOM отнимают много времени, потому что часто требуется повторный рендеринг всей страницы или определенной части, поэтому понимание того, как оптимизировать взаимодействие с DOM, может значительно улучшить скорость завершения скрипта. Будет объяснено позже
хранилище массивов
В информатике существует классическая проблема: наилучшая производительность при чтении и записи достигается за счет изменения места хранения данных, которое связано со скоростью извлечения данных во время выполнения кода. В JS эта проблема относительно проста, так как есть только 4 варианта.
- буквальный Литерал представляет сам себя и не хранится в определенном месте. Литералы JS: строки, числа, логические значения, объекты, массивы, функции, регулярные выражения и специальные значения null, undefined
- локальная переменная Единица хранения данных, определенная с помощью var
- элемент массива Хранится внутри объекта JS, индексируется числом
- член объекта Хранится внутри объекта JS, индексируется строкой Каждый тип места хранения данных имеет разные затраты на чтение и запись. В большинстве случаев стоимость массивов и объектов немного выше, а конкретная производительность зависит от производительности браузера и интерпретатора js. Попробуйте использовать литералы и локальные переменные, чтобы сократить использование элементов массива и членов объекта.
объем
Понимание концепции области действия — это ключ к JS и ядру не только с точки зрения производительности, но и с функциональной точки зрения. Проще говоря: эффективная область (домен), к каким переменным может обращаться функция, назначение this и преобразование контекста. Когда дело доходит до масштаба, вы не можете обойти цепочку масштабов. Область действия понимается путем понимания цепочек областей действия и идентификаторов.
Цепочка областей видимости и разрешение идентификаторов
Каждая функция является экземпляром объекта Function, который, как и любой другой объект, имеет свойства, к которым можно получить доступ программно, а также ряд внутренних свойств, к которым нельзя получить доступ через код, но которые доступны только для механизма JS. Одним из внутренних свойств является [[Scope]], как определено третьей редакцией стандарта ECMA-262.
Внутреннее свойство [[Scope]] содержит коллекцию объектов в области, в которой была создана функция. Этот набор называется цепочкой области действия функции, которая определяет, к каким данным может получить доступ функция.Каждый объект в области действия функции называется изменяемым объектом, и каждый изменяемый объект существует в форме «пары ключ-значение». Когда функция создается, ее цепочка областей видимости заполняется объектами данных, доступными в той области, в которой была создана функция. Например:
function fn(a,b){
return res = a*b;
}
При создании fn вставляет объектную переменную в свою цепочку областей видимости, которая представляет собой все переменные, определенные в глобальной области видимости. Глобальный объект содержит окно, навигатор, документ и т. д. Область действия используется при выполнении fn, а среда выполнения также называется контекстом выполнения. Он определяет среду, когда функция выполняется, даже если это одна и та же функция, новая среда создается каждый раз, когда она выполняется, и среда уничтожается после выполнения функции. Каждая среда разрешает параметры, переменные в соответствии с областью действия и цепочкой областей. Можно понять, что цепочка областей действия подобна стеку, а вершина стека — это текущий активный объект (набор объектов в свойстве [[Scope]] функции при создании среды), в большинстве случаев , его также можно понимать как локальную переменную, определенную внутри функции.
Закрытие на основе JS-функции позволяет получить доступ к данным за пределами локальной области, хотя это вызовет проблемы с производительностью, потому что среда выполнения, хотя и уничтожает, но активирует объект, все еще существует, он может кэшировать переменные, поэтому нет глобального объекта . Быть применимым
объект
Атрибуты и методы являются членами объекта, ссылка на функцию является методом, а не функция является атрибутом. Почему доступ к объектам медленный? Из-за проблемы с цепочкой прототипов.
Прототипы и цепочки прототипов
смотри прямо на код
function fun(name,age){
this.name = name+'';
this.age = age
}
fun.prototype.getName = function(){
return this.name;
}
var fn = new fun();
true = (fn instanceof fun) //true
true = (fn instanceof Object)
fn.__proto__ = fun.prototype
/*
* fun的原型方法
__proto__ = null
hasOwnProperty = (function)
isPrototypeOf = (function)
propertyIsEnumerable = (function)
toLocaleString = (function)
toString = (function)
valueOf = (function)
*/
Обычно обычные переменные такие, уровень до корня (окна), без этой переменной или свойства или метода, он возвращает значение undefined;
DOM-программирование
Манипулирование DOM является дорогостоящим, что является наиболее распространенным узким местом производительности веб-приложений.Модуль объекта документа (DOM) не зависит от языка, программный интерфейс для управления документами xml и html и реализован в браузерах через js. Браузерные движки рендеринга и js-интерпретации у разных компаний разные, знаменитый V8, как всем известно, — это js-движок, а рендеринг Chrome — WebCore. Каждый браузер имеет два набора интерпретаторов, которые относительно независимы. Это означает, что для каждой операции требуется (V8WebCore)==>браузер. Оба интерпретатора требуют затрат на подключение и связь. Сокращение связи между двумя интерпретаторами и уменьшение частоты смены страниц — это направление оптимизации.
Перерисовать перерисовать и оплавить оплавление
Каждому узлу в дереве DOM, который необходимо отобразить, соответствует хотя бы один узел в дереве рендеринга, но нет скрытых (display:none) элементов DOM; узлы дерева рендеринга называются фреймами (frames) боксами (boxes), После построения DOM и дерева рендеринга браузер начинает отрисовывать элементы страницы (paint)
Когда происходит перекраска? Когда геометрические свойства страницы изменяются, что влияет на существующий поток документов, и макет страницы необходимо перенастроить. Приведу несколько примеров:
- Добавляйте или удаляйте видимые элементы DOM;
- Меняется положение элемента DOM;
- изменение размера элемента DOM: отступы контейнера, границы, изменения свойств полей и т. д.;
- Изменения содержимого в контейнере вызывают изменение ширины и высоты: количество строк текста увеличивается (уменьшается), изображение сворачивается, а изображение заменяется другим более крупным изображением.
- Инициализация окна браузера и изменение размера После того, как перестановка окончена, ее нужно перерисовать. Поэтому, чтобы максимально избежать генерации перестановки, чтобы избежать или меньше перерисовки и перестановки, необходимо как можно меньше обращаться к некоторым переменным:
offsetTop、offsetLeft、offsetWidth、offsetHeight
scrollTop、scrollLeft、scrollWidth、scrollHeight
clientTop、clientLeft、clientWidth、clientHeight
getComputedStyle() (currentStyle in IE)
function scroller(){
var H = document.body.offsetHeight || scrollHeight
return function(){
var args = arguments,ct = this;
// your code
}
}
Чтобы иметь наименьшее влияние на перерисовку и перекомпоновку, вы должны как можно меньше изменять DOM и получать доступ к свойствам, влияющим на перекомпоновку. Если вам нужно изменить его, постарайтесь выполнить три шага: 1. Элементы находятся вне потока документа 2. Примените сразу несколько изменений 3. Вернуться к документообороту И первый, и третий этапы будут переставлены, поэтому ядро по-прежнему остается вторым этапом. Теперь, когда виртуальный дом большой 🔥, мы можем немного понять основную практику. Несколько методов разового обновления: метод string или array.join('') innerHTML, createElement appendChild в конце, document.createDocumentFragment, cloneNode необходимо изменить узел на узел кеша и заменить его. Кроме того, анимацию тоже нужно как можно меньше перерисовывать и перекомпоновывать, например: по диагонали, двигаясь из левого верхнего в правый нижний угол
function move2RB(){
var dom = document.getElementById('id'),curent = dom.style.top;
while(curent<500){
curent++
dom.style.cssText = 'left:'+curent+'px; top:'+curent+'px';
}
}
// 不要写成每次都去获取,left=dom.style.left再加1,甚至是dom.style.left = (pareSint(dom.style.left,10)+1)+'px'这种写法,直接改变className也是可以的。
Если резюмировать в нескольких словах: меньше доступа к DOM, разовая модификация после обработки и расчета в js, хорошее использование кеша и нативного API, используя текущие три фреймворка (angular, react, vue), у вас нет беспокоиться об этом :)
Алгоритмы и управление процессами
Общая структура кода является одним из основных факторов, влияющих на скорость бега, и число не обязательно пропорционально скорости бега. Организационная структура, мышление и эффективность исполнения являются основой! ! JS относится к категории ECMA. Это язык сценариев. Многие элементы управления процессами и инженерные идеи заимствованы из таких языков, как java и c. Поэтому нам поможет знание кодирования и разработки внутреннего языка. понимание.
петля
- for
for(var i=0;i<10;i++){
// code
}
Обратный порядок может немного повысить эффективность, когда имеется большое количество данных, i почтовая петля За исключением цикла for-in, другие эффективности аналогичны, поэтому есть только две точки, которые могут повысить эффективность. Если иначе VS переключатель
Чем больше количество условий, тем выше итерация эффективность выключателя; когда есть только один выбор или простое суждение о том, если-иначе, читаемость лучше. В фактическом кодировании, если в некоторых случаях есть только два варианта, если в некоторых случаях, если-else не используется, тройная операция может использоваться: результат = (True || false)? Состояние0: условие1; и писать наиболее вероятное условие Если () ", уменьшите количество суждений, а также путем продления, вероятность судебных решений, если они должны быть от большого до малого. Даже дихотомия можно использовать: Конечно, это простые каштаны, есть много других способов ввести алгоритм в коде, повысить эффективность, такую как выход недели Вы можете хранить строки, переменные и методы в массивах или объектах. Поскольку это ссылка, эффективность также очень высока 1. Рекурсия Однако, если конечное условие рекурсии не ясно, она будет продолжать работать, и страница не будет отвечать в течение длительного времени. В анабиозе! ! Кроме того, «стек вызовов» каждого браузера ограничен. Желающие могут поэкспериментировать сами. Чтобы избежать этой проблемы, в дополнение к явному конечному условию вы также можете использовать «хвостовую рекурсию».
2. Хвостовая рекурсия Используя функцию закрытия, метод может хранить вычисляемые данные или переменные, такие как перезапись факториальной функции. *?+
Эта часть также требует достаточно большого пространства, поэтому занимает первое место. . . Старомодный контент, если страница открывается за секунды, какие моменты можно оптимизировать? Прямой серверный рендеринг, оптимизация домашней страницы, отложенная загрузка компонентов, bigpipe, мониторинг производительности и целевая оптимизация и т. д. Сначала выкопайте яму, а я поделюсь собственным опытом в специальной статье. Учитель Жуань Ифэн сказал лучше, пожалуйста, перейдите по ссылкеThis link API Worker Pool был предложен на основе подключаемого модуля Google Gears, который является «прототипом» Web Workers, изначально надеясь улучшить функции браузера, такие как поддержка автономного просмотра (автономный доступ к кэшированным страницам и отправка автономных операций после повторной загрузки). запуск), но (2015/11) устарела. Начиная с HTML5, Web Workers API был выделен в отдельную спецификацию. С тех пор мы можем поместить вычисления, кодек, настоящие асинхронные запросы и т. д. в Web Workers. worker.js Так устанавливается "похожая" многопоточность фронтенда список инструментов отладки Инструменты оптимизации, пожалуйста, ищите подробности, вы не будете носильщиком.var i=0;
while(i<10){
// code
i++;
}
var i=0;
do{
// code
}while(i++<10)
for(var prop in object){
// code
}
Условное суждение
//---假设某参数的值非正即负,或查询二叉树,或查询不同SP的手机号
if(parse>0){
if(parse>10){
//code
}else if(parse<5&&parse>1){
//code
}else{
}
}else{
//code 负数处理
}
function getweek(){
var w = ['日','一','二','三','四','五','六'],
now = new Date(),
d = now.getDay();
return '星期'+w[d];
}
рекурсия
//---阶乘
function facttail(n){
if(n==0){
return 1;
}else{
return n*facttail(n-1);
}
}
//---幂次方
function fn(s,n){
if(n==0){
return 1;
}else{
return s*fn(s,n-1);
}
}
//---阶乘
function facttail(n,res){
if(n<0){
return 0;
}else if(n==0){
return 1;
}else if(n==1){
return res;
}else{
return facttail(n-1, n*res);
}
}
//---幂次方
function fn(s,n){
if(n==0){
return 1;
}else{
return s*fn(s,n-1);
}
}
кэш-память
function memfacttail(n){
if(!memfacttail.cache){
memfacttail.cache = {
"0":1,
"1":1
};
}
if(!memfacttail.cache.hasOwnProperty(n)){
memfacttail.cache.n = n * memfacttail(n-1);
}
return memfacttail.cache.n;
}
Строки и регулярные
Адаптивная страница
поток браузера
event loop
Web Workers
// html中直接写
var worker = new Worker('worker.js')
// 或通过主页面的js文件调用,例如:main.js
//---主页面
if (window.Worker) {
var worker = new Worker('worker.js');
var data = {a: 1, b: [1, 2, 3], c: 'string'};
worker.postMessage(data);
worker.onmessage = function(e) {
console.log('main thread received data');
console.log(e.data);
// 接到消息立即停止worker,onerror将不会触发
// worker.terminate();
// terminate之后收不到后续消息,但post不报错
// worker.postMessage(1);
}
worker.onerror = function(err) {
console.log('main thread received err');
console.log(err.message);
// 阻止报错
err.preventDefault();
}
}
//---处理js,可以引入其它依赖
// importScripts('lib.js');
// importScripts('a.js', 'b.js');
onmessage = function(e) {
console.log(self); // 看看global变量身上有些什么
var data = e.data;
console.log('worker received data');
console.log(data);
var res = data;
res.resolved = true;
postMessage(res);
setTimeout(function() {
throw new Error('error occurs');
// close,立即停止,相当于主线程中的worker.terminate()
// close();
}, 100);
};
//---main
var worker = new Worker('worker.js')
worker.onmessage = (e)=>{
var jsonData = e.data // 回传回来的数据
evaluateData(jsonData)
}
worker.postmessage(jsonText)
///---worker
self.onmessage = (e){
var jsonText = e.data // main传过来的数据
var jsonData = JSON.parse(jsonText) // 解析转换
self.postMessage(jsonData)
}
//---main
var sWorker = new SharedWorker('worker.js')
sWorker.port.start()
//---first
first.onchange = function() {
sWorker.port.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
//---first
second.onchange = function() {
sWorker.port.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
sWorker.port.onmessage = function(e) {
result1.textContent = e.data;
console.log('Message received from worker');
}
инструмент