От многопроцессорности браузера до однопоточности JS — наиболее полное сочетание операционного механизма JS.

внешний интерфейс

предисловие

Мнения ограничены.Если есть какое-либо неподходящее описание, пожалуйста, помогите указать его вовремя.Если есть какая-то ошибка, она будет исправлена ​​вовремя.

----------Супер длинный текст + предупреждение о нескольких картинках, это занимает много времени. ------------

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

---------- Начало текста ----------

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

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

Содержание такое: от процесса браузера, к работе ядра браузера, к единому потоку движка JS, к механизму циклов событий JS, разобрать систему от начала до конца, избавиться от фрагментации и сформировать система знаний

Цель: после прочтения этой статьи у меня должно быть определенное представление о многопроцессорности браузера, однопоточности JS и механизме циклов обработки событий JS. Существует скелет свода знаний, а не чувство невежества.

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

контур

  • Различие между процессами и потоками

  • Браузеры являются многопроцессорными

    • Какие процессы включены в браузере?

    • Преимущества многопроцессорности браузера

    • Основное внимание уделяется ядру браузера (процесс рендеринга)

    • Процесс связи между процессом браузера и ядром браузера (процесс рендерера)

  • Объединение отношений между потоками в ядре браузера

    • Поток рендеринга GUI и поток движка JS являются взаимоисключающими.

    • JS блокирует загрузку страницы

    • WebWorker, многопоточность для JS?

    • WebWorker и SharedWorker

  • Просто разберитесь с процессом рендеринга в браузере

    • Последовательность события загрузки и события DOMContentLoaded

    • Блокирует ли загрузка css рендеринг дерева dom?

    • Нормальные и составные слои

  • Говоря о механизме запуска JS из цикла событий

    • Механизм событийного цикла дополнительно дополнен

    • Говорить только о таймере

    • setTimeout вместо setInterval

  • Расширенный цикл обработки событий: макрозадача и микрозадача

  • последние слова

Различие между процессами и потоками

Неразличимое различие между потоками и процессами — ошибка многих новичков, и это не имеет значения. Это нормально. Рассмотрим следующую метафору:

- 进程是一个工厂,工厂有它的独立资源

- 工厂之间相互独立

- 线程是工厂中的工人,多个工人协作完成任务

- 工厂内有一个或多个工人

- 工人之间共享空间

Уточните и усовершенствуйте концепцию:

- 工厂的资源 -> 系统分配的内存(独立的一块内存)

- 工厂之间的相互独立 -> 进程之间相互独立

- 多个工人协作完成任务 -> 多个线程在进程中协作完成任务

- 工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成

- 工人之间共享空间 -> 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)

А затем закрепить:

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

Итак, должно быть проще понять:Процесс — это наименьшая единица выделения ресурсов ЦП (система выделит ему память)

Наконец, опишем это снова в более официальных терминах:

  • Процесс — это наименьшая единица распределения ресурсов ЦП (наименьшая единица, которая может владеть ресурсами и работать независимо).

  • Поток — это наименьшая единица планирования процессора (поток — это единица программы, работающей на основе процесса, и в процессе может быть несколько потоков).

tips

  • Связь между различными процессами также возможна, но по более высокой цене.

  • Теперь общее общее название:однопоточный против многопоточного, все относятся квнутри процессаодиночные и множественные. (Так что ядро ​​все равно должно принадлежать процессу)

Браузеры являются многопроцессорными

Поняв разницу между процессом и потоком, давайте в определенной степени разберемся с браузером: (сначала посмотрите на упрощенное понимание)

  • Браузеры являются многопроцессорными

  • Браузер может работать, потому что система выделяет ресурсы (процессор, память) для своего процесса.

  • Проще говоря, каждый раз, когда открывается вкладка, это эквивалентно созданию независимого процесса браузера.

Что касается проверки вышеуказанных пунктов,Пожалуйста сделайте первое фото:

открыть на картинкеChromeнесколько вкладок браузера, которые затем можно найти вChrome的任务管理器Процессов несколько (соответственно каждая вкладка имеет отдельный процесс и основной процесс). Если вы заинтересованы, вы можете попробовать это сами.Если вы откроете другую вкладку, процесс обычно +1 или больше.

**Примечание.** В браузере также должен быть свой механизм оптимизации. Иногда после открытия нескольких вкладок в диспетчере задач Chrome можно увидеть, что некоторые процессы объединены. (Поэтому не обязательно абсолютно, чтобы каждая метка Tab соответствовала процессу)

Какие процессы включены в браузере?

Узнав, что браузер является многопроцессорным, давайте посмотрим, какие процессы он содержит: (Для упрощения понимания перечислены только основные процессы)

  1. Браузерный процесс: Основной процесс браузера (отвечает за координацию и главный контроль) есть только один. роль имеет

    • Отвечает за отображение интерфейса браузера и взаимодействие с пользователем. например вперед, назад и т.

    • Отвечает за управление каждой страницей, создание и уничтожение других процессов

    • Нарисуйте растровое изображение в памяти, полученное процессом Renderer, в пользовательский интерфейс.

    • Управление сетевыми ресурсами, загрузка и т. д.

  2. Процессы сторонних подключаемых модулей: один процесс для каждого типа подключаемого модуля, создаваемый только при использовании подключаемого модуля.

  3. Процесс GPU: максимум один, для 3D-рисования и т. д.

  4. Процесс рендеринга браузера (ядро браузера) (процесс рендеринга, который является многопоточным внутри): по умолчанию один процесс на странице вкладки не влияет друг на друга. Главная роль

    • Рендеринг страницы, выполнение скрипта, обработка событий и т. д.

Укрепить память:Открытие веб-страницы в браузере эквивалентно запуску нового процесса (со своей многопоточностью в процессе)

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

Кроме того, через Chrome更多工具 -> 任务管理器самопроверка

Преимущества многопроцессорности браузера

По сравнению с однопроцессорными браузерами, многопроцессорные имеют следующие преимущества:

  • Избегайте сбоя одной страницы, влияющего на весь браузер

  • Избегайте сбоев сторонних подключаемых модулей, влияющих на весь браузер.

  • Многопроцессорность: используйте все преимущества многоядерности

  • Модель песочницы удобно использовать для изоляции таких процессов, как плагины, и повышения стабильности браузера.

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

Конечно, потребление таких ресурсов, как память, тоже будет больше, а значит, пространство разменивается на время.

Основное внимание уделяется ядру браузера (процесс рендеринга)

Дело в том, что мы видим, что процессов, упомянутых выше, так много, так что в конечном счете нужна обычная фронтенд-операция? ответпроцесс рендеринга

Таким образом, можно понять, что в этом процессе выполняется рендеринг страницы, выполнение JS и цикл событий. Затем сосредоточьтесь на анализе этого процесса.

Имейте в виду, что процесс рендеринга в браузере является многопоточным.(Если вы этого не понимаете,Пожалуйста, обратите внимание на различие между процессами и потоками.)

Наконец-то пришла к концепции треда 😭, так любезно. Затем давайте посмотрим, какие потоки он содержит (перечислите некоторые основные резидентные потоки):

  1. Поток рендеринга графического интерфейса

    • Отвечает за рендеринг интерфейса браузера, анализ HTML, CSS, построение дерева DOM и дерева RenderObject, компоновку и рисование и т. д.

    • Когда интерфейс нужно перекрасить (Repaint) или вызвать какую-то операцию, чтобы вызвать перекомпоновку (reflow), этот поток будет выполняться

    • Уведомление,Поток рендеринга GUI и поток движка JS являются взаимоисключающими., при выполнении JS-движка поток графического интерфейса будет приостановлен (эквивалентно замораживанию), а обновления графического интерфейса будут сохранены в очереди.Подождите, пока JS-движок не будет простаиватьбыть исполнены немедленно.

  2. Поток движка JS

    • Также известно как ядро ​​JS, отвечающее за обработку скриптов Javascript. (например, двигатель V8)

    • Поток движка JS отвечает за синтаксический анализ сценариев Javascript и выполнение кода.

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

    • Также обратите внимание, чтоПоток рендеринга GUI и поток движка JS являются взаимоисключающими., поэтому, если время выполнения JS слишком велико, рендеринг страницы будет несогласованным, что приведет к блокировке рендеринга и загрузки страницы.

  3. поток триггера события

    • Он принадлежит браузеру, а не движку JS, и используется для управления циклом событий (понятно, что движок JS не может быть занят сам по себе, и браузеру нужно открыть другой поток, чтобы помочь)

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

    • Когда соответствующее событие удовлетворяет условиям срабатывания и срабатывает, поток добавит событие в конец очереди ожидания и будет ждать, пока JS-движок обработает его.

    • Обратите внимание, что из-за однопоточной связи JS события в этих ожидающих очередях должны быть поставлены в очередь для обработки механизмом JS (выполняются только тогда, когда механизм JS простаивает).

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

    • легендарныйsetIntervalа такжеsetTimeoutнить

    • Счетчик времени браузера не учитывается механизмом JavaScript (поскольку механизм JavaScript является однопоточным, если он находится в состоянии заблокированного потока, это повлияет на точность времени)

    • Поэтому для определения времени и запуска времени используется отдельный поток (после того, как время завершено, оно добавляется в очередь событий и выполняется после ожидания простоя JS-движка).

    • Обратите внимание, что W3C предусматривает в стандарте HTML, что временной интервал менее 4 мс в setTimeout должен считаться 4 мс.

  5. Асинхронный поток HTTP-запросов

    • После подключения XMLHttpRequest через браузер открывается новый запрос потока.

    • При обнаружении изменения состояния, если установлена ​​функция обратного вызова, асинхронный потокГенерация события изменения состояния, снова поместите этот обратный вызов в очередь событий. А затем выполняется движком JavaScript.

Видя это, если вы чувствуете усталость, вы можете сначала отдохнуть Эти понятия нужно переварить, ведь механизм цикла событий, о котором будет сказано позже, основан на事件触发线程, так что если вы просто посмотрите на фрагмент разрозненных знаний, Может возникнуть чувство непонимания. Расчесывание, которое необходимо завершить, может быть быстро ускорено, и его нелегко забыть. Поместите картинку, чтобы закрепить ее:

Еще один момент, почему JS-движок однопоточный? Ну, на этот вопрос не должно быть стандартного ответа, например, это может быть только из-за сложности многопоточности, например многопоточных операций, которые обычно требуют блокировки, поэтому изначально был разработан один поток. . .

Процесс связи между процессом браузера и ядром браузера (процесс рендерера)

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

Если открыть диспетчер задач самостоятельно, а затем открыть браузер, то можно увидеть:В диспетчере задач появляются два процесса (один — главный процесс, а другой — процесс рендеринга, открывающий вкладку), Затем в этой предпосылке посмотрите на весь процесс: (значительно упростил)

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

  • Интерфейс Renderer процесса Renderer получает сообщение, дает краткое объяснение потоку рендеринга, а затем начинает рендеринг.

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

    • Конечно, могут быть потоки JS, управляющие DOM (что может вызвать перекомпоновку и перерисовку).

    • Наконец, процесс рендеринга передает результат процессу браузера.

  • Процесс браузера получает результат и рисует результат

Вот простая схема: (очень упрощенно)

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

Если говорить об этом подробнее, то это включает в себя анализ исходного кода ядра браузера, что не входит в рамки данной статьи.

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

Объединение отношений между потоками в ядре браузера

На данный момент у нас есть общее представление о работе браузера Далее мы кратко разберем некоторые понятия.

Поток рендеринга GUI и поток движка JS являются взаимоисключающими.

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

Поэтому, чтобы предотвратить непредсказуемые результаты рендеринга, браузер устанавливает поток рендеринга GUI и механизм JS как взаимоисключающие.Когда механизм JS выполняется, поток графического интерфейса будет приостановлен. Обновления графического интерфейса хранятся в очереди и выполняются, как только поток JS-движка простаивает.

JS блокирует загрузку страницы

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

Например, если предположить, что JS-движок выполняет огромное количество вычислений, даже если в это время будет обновлен графический интерфейс, он будет сохранен в очереди и выполнен после простоя JS-движка. Тогда из-за огромного объема вычислений JS-движок, скорее всего, будет долго простаивать, и естественно почувствует, что огромная карта несравнима.

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

WebWorker, многопоточность для JS?

Как упоминалось в предыдущей статье, движок JS является однопоточным, а время выполнения JS слишком велико, чтобы заблокировать страницу, так что действительно ли JS бессилен для вычислений с интенсивным использованием ЦП?

Итак, позже поддержка HTML5Web Worker.

Официальное объяснение от MDN:

Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面

一个worker是使用一个构造函数创建的一个对象(e.g. Worker()) 运行一个命名的JavaScript文件 

这个文件包含将在工作线程中运行的代码; workers 运行在另一个全局上下文中,不同于当前的window

因此,使用 window快捷方式获取当前全局的范围 (而不是self) 在一个 Worker 内将返回错误

Поймите это так:

  • При создании Worker движок JS обращается к браузеру для открытия дочернего потока (дочерний поток открывается браузером, полностью контролируется основным потоком и не может управлять DOM)

  • Поток движка JS и рабочий поток взаимодействуют определенным образом (API postMessage, вам необходимо сериализовать объект для взаимодействия с конкретными данными потока)

Поэтому, если есть очень трудоемкая работа, пожалуйста, открывайте отдельный поток Worker, чтобы, как бы он ни был потрясен, он не повлиял на основной поток JS-движка. Только после вычисления результата передайте результат в основной поток, отлично!

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

Другие, подробное объяснение Worker выходит за рамки этой статьи, поэтому я не буду повторяться.

WebWorker и SharedWorker

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

  • WebWorker принадлежит только определенной странице и не будет использоваться совместно с процессом рендеринга (процессом ядра браузера) других страниц.

    • Таким образом, Chrome создает новый поток в процессе рендеринга (каждая вкладка — это процесс рендеринга) для запуска программы JavaScript в Worker.
  • SharedWorker используется всеми страницами браузера и не может быть реализован так же, как Worker, поскольку он не связан с процессом рендеринга и может совместно использоваться несколькими процессами рендеринга.

    • Таким образом, браузер Chrome создает отдельный процесс для SharedWorker для запуска программ JavaScript.Существует только один процесс SharedWorker для каждого идентичного JavaScript в браузере, независимо от того, сколько раз он создается.

Видя это, должно быть легко понять, в чем, по сути, разница между процессом и потоком. SharedWorker управляется независимым процессом, WebWorker — это просто поток в процессе рендеринга.

Просто разберитесь с процессом рендеринга в браузере

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

Для упрощения понимания предыдущая работа просто опущена как: (будет дополнена или может быть написана другая сверхдлинная статья)

- 浏览器输入url,浏览器主进程接管,开一个下载线程,
然后进行 http请求(略去DNS查询,IP寻址等等操作),然后等待响应,获取内容,
随后将内容通过RendererHost接口转交给Renderer进程

- 浏览器渲染流程开始

После того, как ядро ​​браузера получит контент, рендеринг можно условно разделить на следующие этапы:

  1. Разобрать html для построения дерева домов

  2. Анализ CSS для построения дерева рендеринга (разбор кода CSS в древовидную структуру данных, а затем объединение DOM в дерево рендеринга)

  3. Дерево рендеринга макета (Layout/reflow), отвечающее за расчет размера и положения каждого элемента

  4. Отрисовка дерева рендеринга (краска), отрисовка информации о пикселях страницы

  5. Браузер отправит информацию о каждом слое на графический процессор, а графический процессор скомпонует каждый слой и отобразит его на экране.

Все подробные шаги были опущены, после рендерингаloadПосле события оно обрабатывается собственной логикой JS.

Поскольку некоторые подробные шаги были опущены, давайте упомянем некоторые детали, которые могут потребовать внимания.

Вот перерисованное изображение из справочного источника: (сначала справочный источник)

Последовательность события загрузки и события DOMContentLoaded

Как упоминалось выше, он будет запущен после завершения рендеринга.loadсобытие, то можно отличитьloadсобытие иDOMContentLoadedПоследовательность событий?

Это так же просто, как знать их определения:

  • Когда событие DOMContentLoaded срабатывает, только при загрузке DOM, за исключением таблиц стилей, изображений.

(Например, если есть скрипт, загруженный асинхронно, он может не завершиться)

  • Когда срабатывает событие onload, все DOM, таблицы стилей, скрипты и изображения на странице уже загружены.

(рендеринг)

Итак, последовательность такая:DOMContentLoaded -> load

Блокирует ли загрузка css рендеринг дерева dom?

Вот случай введения css в голову

Во-первых, все мы знаем:CSS загружается асинхронно отдельным потоком загрузки.

Тогда поговорим о следующих явлениях:

  • Загрузка CSS не блокирует синтаксический анализ дерева DOM (DOM строится как обычно при асинхронной загрузке)

  • Но это заблокирует рендеринг дерева рендеринга (при рендеринге нужно дождаться загрузки css, потому что дереву рендеринга нужна информация css)

Это также может быть механизм оптимизации браузера.

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

Нормальные и составные слои

упоминается на этапе рендерингаcompositeконцепция.

Таким образом, можно просто понять, что слои, отображаемые браузером, обычно включают две категории:普通图层так же как复合图层

Прежде всего, обычный документооборот можно понимать как составной слой (здесь называемый默认复合层, независимо от того, сколько элементов добавлено, фактически они находятся в одном составном слое)

Во-вторых, абсолютная компоновка (то же, что и фиксированная), хотя ее можно отделить от обычного потока документов, она все же принадлежит默认复合层.

Затем вы можете пройти硬件加速способ объявить新的复合图层, который распределяет ресурсы индивидуально (Конечно, это тоже будет вне обычного документооборота, так что какие бы изменения в этом композитном слое не коснулись默认复合层Перерисовать в )

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

МогуChrome源码调试 -> More Tools -> Rendering -> Layer bordersСмотрите, желтый цвет — это информация о композитном слое.

Как показано ниже. Вышеприведенное утверждение можно проверить

Как превратиться в композитный слой (аппаратное ускорение)

Превращение элемента в составной слой — легендарная технология аппаратного ускорения.

  • Самый распространенный способ:translate3d,translateZ

  • opacityАнимации свойств/переходов (композитные слои будут создаваться только в процессе выполнения анимации, и элементы вернутся в свое предыдущее состояние после того, как анимация не начнется или не закончится)

  • will-changАтрибут (это относительно удаленный), обычно используемый с непрозрачностью и переводом (и после тестирования, в дополнение к вышеперечисленным атрибутам, которые могут запускать аппаратное ускорение, другие атрибуты не станут составными слоями),

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

  • <video><iframe><canvas><webgl>и т.д. элементы

  • Другие, такие как предыдущий флэш-плагин

Разница между абсолютным и аппаратным ускорением

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

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

Что делает составной слой?

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

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

Пожалуйста, используйте индекс при аппаратном ускорении

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

Конкретный принцип заключается в следующем:В webkit CSS3, если к этому элементу добавлено аппаратное ускорение и уровень индекса относительно низкий, Затем за этим элементом находятся другие элементы (уровень выше этого элемента или такой же, а относительные или абсолютные атрибуты одинаковые), По умолчанию он станет рендерингом составного слоя. Если его не обработать должным образом, это сильно повлияет на производительность.

Чтобы понять это просто, его можно рассматривать как неявную концепцию синтеза:Если a является составным слоем, а b находится поверх a, то b также неявно преобразуется в составной слой., что требует особого внимания

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

web.jobbole.com/83575/

Говоря о механизме запуска JS из цикла событий

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

Обратите внимание, здесь не обсуждается可执行上下文,VO,scop chainи другие концепции (они могут быть организованы в отдельную статью), здесь в основном комбинацияEvent LoopПоговорим о том, как выполняется JS-код.

Предпосылка чтения этой части заключается в том, что вы уже знаете, что движок JS является однопоточным, и здесь будут использоваться несколько концепций из вышеизложенного: (Если вы не очень хорошо это понимаете, вы можете вернуться и просмотреть его)

  • Поток движка JS

  • поток триггера события

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

Тогда поймите другую концепцию:

  • JS делится на синхронные задачи и асинхронные задачи.

  • Задачи синхронизации выполняются в основном потоке, формируя执行栈

  • вне основного потока,поток триггера событияуправлять任务队列, пока асинхронная задача имеет работающий результат, в任务队列Поместите в него событие.

  • однажды执行栈После выполнения всех задач синхронизации в任务队列, добавьте исполняемую асинхронную задачу в исполняемый стек и начните выполнение.

Посмотрите на картинку:

Глядя на это, должно быть понятно: почему иногда события, отправляемые setTimeout, не могут быть выполнены вовремя? Поскольку он может быть помещен в список событий, основной поток не бездействует, выполняет другой код, Так что естественно есть ошибки.

Механизм событийного цикла дополнительно дополнен

Вот прямая ссылка на картинку, чтобы помочь понять: (см. речь Филипа Робертса "Help, I'm stuck in an event-loop")

Картинка выше примерно описывается как:

  • Когда запускается основной поток, создается стек выполнения.

Когда код в стеке вызывает некоторые API, они будут добавлять различные события в очередь событий (при выполнении условий триггера, таких как завершение ajax-запроса)

  • После того, как код в стеке будет выполнен, он прочитает события в очереди событий и выполнит эти обратные вызовы.

  • так цикл

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

Говорить только о таймере

Ядром вышеуказанного механизма цикла событий является: поток движка JS и поток триггера события.

Но в событии есть некоторые скрытые детали, такие как вызовsetTimeoutКак он ждет определенное время перед добавлением в очередь событий?

Обнаружен ли он движком JS? Конечно, нет. это изпоток таймераКонтроль (потому что движок JS слишком занят и ему некогда этим заниматься)

Зачем отдельный поток таймера? Поскольку движок JavaScript является однопоточным, если поток заблокирован, это повлияет на точность синхронизации, поэтому необходимо открыть отдельный поток для синхронизации.

Когда бы вы использовали поток таймера?когда используешьsetTimeoutилиsetIntervalВремя, который требует отсчета времени потока таймера и помещает конкретное событие в очередь событий по истечении времени.

Например:

setTimeout(function(){
    console.log('hello!');
}, 1000);

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

setTimeout(function(){
    console.log('hello!');
}, 0);

console.log('begin');

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

Уведомление:

  • Результат выполнения:beginназадhello!

  • Хотя первоначальное намерение кода состоит в том, чтобы отправить очередь событий через 0 миллисекунд, W3C предусматривает в стандарте HTML, что временной интервал менее 4 мс в setTimeout должен считаться 4 мс.

(Но есть также поговорка, что разные браузеры имеют разные настройки минимального времени)

  • Даже если он не ждет 4 мс, даже если он будет помещен в очередь событий, предполагая 0 мс, он будет выполнен первымbegin(Поскольку очередь событий будет активно считываться только после того, как исполняемый стек станет пустым)

setTimeout вместо setInterval

Существует разница между использованием setTimeout для имитации периодического хронирования и прямым использованием setInterval.

Потому что каждый раз, когда setTimeout отсчитывается по времени, он будет выполняться, а затем setTimeout будет продолжаться после периода выполнения, и в середине будет больше ошибок. (Насколько ошибка связана со временем выполнения кода)

И setInterval каждый раз отправляет событие с точным интервалом. (Однако фактическое время выполнения события не обязательно точное. Также возможно, что событие еще не выполнено, а следующее событие придет)

И у setInterval есть несколько фатальных проблем:

  • Совокупный эффект (упомянутый выше), если код setInterval не закончил выполнение до того, как (setInterval) снова добавляется в очередь,

Это приведет к тому, что код таймера будет запускаться несколько раз подряд без промежутков между ними. Даже при выполнении с нормальными интервалами время выполнения кода для нескольких setInterval может быть короче, чем ожидалось (поскольку выполнение кода занимает определенное время).

  • Например, в таких браузерах, как iOS webview или Safari, есть такая функция,JS не выполняется при прокрутке, если вы используете setInterval, вы обнаружите, что после окончания прокрутки обратный вызов накопления JS будет выполняться много раз, потому что прокрутка не выполняет обратный вызов накопления JS.Если время выполнения обратного вызова слишком велико, это приведет к тому, что контейнер зависание и некоторые непонятные ошибки.(Этот раздел будет дополнен позже. Оптимизация, которая идет с setInterval, не будет добавлять обратные вызовы повторно)

  • И когда браузер свернут и отображается, setInterval не выполняет программу,

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

Таким образом, с учетом стольких проблем лучшим решением обычно считается:Используйте setTimeout для имитации setInterval или используйте requestAnimationFrame напрямую в особых случаях.

Дополнение: Как упоминалось в поднятии JS, движок JS будет оптимизировать setInterval.Если в текущей очереди событий есть callback для setInterval, он не будет добавляться повторно. Однако остается еще много вопросов. . .

Расширенный цикл обработки событий: макрозадача и микрозадача

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

Джейк Арчибальд.com/2015/tasks-…

Механизм циклов событий JS был разобран выше, в случае с ES5 этого достаточно, но сейчас, когда ES6 популярен, остаются некоторые проблемы, такие как следующий вопрос:

console.log('script start');

setTimeout(function() {
    console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
    console.log('promise1');
}).then(function() {
    console.log('promise2');
});

console.log('script end');

Хм, его правильная последовательность выполнения такова:

script start
script end
promise1
promise2
setTimeout

Зачем? Потому что в Promise есть новая концепция:microtask

Или, далее, в JS есть два типа задач:macrotaskа такжеmicrotask, в ECMAScript микрозадача называетсяjobs, макрозадачу можно назватьtask

их определение? разница? Простую точку можно понимать следующим образом:

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

    • Каждая задача будет выполнять задачу от начала до конца и не будет выполнять другие задачи.

    • Чтобы обеспечить упорядоченное выполнение внутренних задач JS и задач DOM, браузер будет повторно отображать страницу после завершения выполнения задачи и до начала выполнения следующей задачи.

    (task->渲染->task->...)

  • микрозадача (также известная как микрозадача) может пониматься как задача, которая выполняется сразу после завершения выполнения текущей задачи.

    • То есть после текущей задачи задачи, перед следующей задачей, до рендеринга

    • Так его скорость отклика будет быстрее, чем у setTimeout (setTimeout — это таск), потому что нет необходимости ждать рендеринга

    • То есть после выполнения макрозадачи все микрозадачи, сгенерированные во время ее выполнения, будут выполнены (до рендеринга).

Какие сценарии будут формировать макрозадачи и микрозадачи?

  • макрозадача: блок основного кода, setTimeout, setInterval и т.д. (как видите, каждое событие в очереди событий является макрозадачей)

  • микрозадача: Promise, process.nextTick и т. д.

Дополнение: В среде узла process.nextTick имеет более высокий приоритет, чем Promise, что можно просто понять так: после завершения макрозадачи сначала будет выполнена часть очереди микрозадач nextTickQueue, а затем будет выполнена часть микрозадачи Promise.

Ссылаться на:сегмент fault.com/please/101000001…

Тогда разбирайтесь по ветке:

  • События в макрозадаче помещаются в очередь событий, и эта очередь состоит изпоток триггера событияподдерживать

  • Все микрозадачи в микрозадаче добавляются в очередь микрозадач (Job Queues), ожидая завершения выполнения текущей макрозадачи, и эта очередь состоит изОбслуживание потока движка JS

(Это из моего собственного понимания + предположения, потому что он выполняется незаметно под основным потоком)

Итак, вкратце о механизме работы:

  • Выполнить задачу макроса (получить ее из очереди событий, если ее нет в стеке)

  • Если во время выполнения встречается микрозадача, добавьте ее в очередь задач микрозадачи.

  • После выполнения макрозадачи все микрозадачи в текущей очереди микрозадач выполняются немедленно (выполняются последовательно).

  • Выполняется текущая задача макроса, проверяется рендеринг, а затем поток графического интерфейса берет на себя рендеринг.

  • После рендеринга поток JS продолжает работать и запускает следующую задачу макроса (полученную из очереди событий).

Как показано на рисунке:

Также обратите вниманиеPromiseизpolyfillОтличия от официальной версии:

  • В официальной версии это стандартная форма микрозадачи.

  • Polyfill обычно имитируется setTimeout, поэтому он представлен в виде макрозадачи.

  • Пожалуйста, обратите особое внимание на разницу между этими двумя пунктами.

Обратите внимание, что некоторые браузеры имеют разные результаты выполнения (поскольку они могут выполнять микрозадачи как макрозадачи). Но для простоты некоторые нестандартные браузерные сценарии здесь не описываются (но имейте в виду, что некоторые браузеры могут быть не стандартными)

20180126 Дополнение: используйте MutationObserver для реализации микрозадач

MutationObserver можно использовать для реализации микрозадач (Относится к микрозадаче, приоритет меньше, чем Promise, Обычно это делается, когда промис его не поддерживает)

Это новая функция в HTML5, ее роль заключается в отслеживании изменений DOM, Наблюдатель мутаций уведомляется, когда в дереве объектов DOM происходят какие-либо изменения.

Как и предыдущий исходный код Vue, он использовался для имитации nextTick, Конкретный принцип заключается в создании TextNode и прослушивании изменений содержимого, Затем измените текстовое содержимое этого узла, когда вы хотите, чтобы nextTick, А именно: (исходный код Vue, без изменений)

var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))

observer.observe(textNode, {
    characterData: true
})
timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
}

Соответствующая ссылка на исходный код Vue

Однако текущая реализация Vue (2.5+) nextTick удаляет метод MutationObserver (предположительно, из соображений совместимости), Вместо этого используйте MessageChannel (Конечно, по умолчанию по-прежнему используется Promise, который совместим только в том случае, если он не поддерживается).

MessageChannel — макрозадача, приоритет:MessageChannel->setTimeout, Поэтому nextTick во Vue (2.5+) отличается от реализации в 2.4 и более ранних версиях, так что вам нужно обратить на это внимание.

Здесь не развернуто, можете посмотретьnuggets.capable/post/684490…

последние слова

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

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

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

Наконец, если вам это нравится, пожалуйста, поставьте лайк!

приложение

блог

Первый выпуск2018.01.21в моем личном блоге

Дай Личунь.com/2018/01/21/…

Внутренний push-почтовый ящик

lichun.dlc@alibaba-inc.com

Присылайте свое резюме на мою почту, там должен быть ответ, если вы соответствуете требованиям, переходите сразу во внутренний пуш! ! !

Индивидуальное обслуживание, отвечаю на любые вопросы!

Вы также можете добавить мой WeChat, чтобы узнать больше: a546684355

использованная литература