Javascript — это однопоточный язык. Web-Worker предлагается в последней версии HTML5, но ядро ​​однопоточного javascript не изменилось. Таким образом, все версии javascript «многопоточности» моделируются с помощью одного потока, а вся многопоточность javascript — это бумажный тигр!

2. Цикл событий Javascript

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

  • Задача синхронизации
  • Асинхронная задача

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

Если содержание, которое должно быть выражено картой, выражено словами:

  • Синхронные и асинхронные задачи попадают в разные «места» выполнения, синхронно входят в main thread, асинхронно входят в Event Table и регистрируют функции.
  • Когда указанное действие выполнено, Event Table перемещает эту функцию в Event Queue.
  • Если задача в основном потоке пуста, она перейдет к Event Queue для чтения соответствующей функции и входа в основной поток для выполнения.
  • Описанный выше процесс повторяется снова и снова, что часто называют Event Loop (цикл событий).

Мы не можем не спросить, как мы узнаем, что стек выполнения основного потока пуст? В движке js есть процесс мониторинга, который будет постоянно проверять, не пуст ли стек выполнения основного потока. Как только он станет пустым, он перейдет в очередь событий, чтобы проверить, есть ли какие-либо функции, ожидающие вызова. Говорить дешево, покажи код:

let data = [];
$.ajax({
    url:www.javascript.com,
    data:data,
    success:() => {
        console.log('send success!');
    }
})
console.log('End of code execution!');

Выше приведен простой код запроса ajax:

  • Ajax вводит Event Table и регистрирует успех функции обратного вызова.
  • Выполнить console.log ('end of code execution').
  • Событие ajax завершается, и успешное выполнение функции обратного вызова достигает Event Queue.
  • Основной поток считывает успешность функции обратного вызова из Event Queue и выполняет ее.

Я полагаю, что благодаря приведенному выше тексту и коду у вас есть предварительное представление о порядке выполнения js. Далее давайте изучим расширенную тему: setTimeout.

3. отношения любви-ненависти с setTimeout

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

setTimeout(() => {
    console.log('sleep three seconds!');
},3000)

Постепенно setTimeout использует больше места, и возникают проблемы. Иногда задержка записи составляет 3 секунды, но на самом деле для выполнения функции требуется 5 минут и 6 секунд. В чем дело? Давайте сначала посмотрим на пример:

setTimeout(() => {
    task();
},3000)
console.log('exec console!');

Согласно нашему предыдущему выводу, setTimeout является асинхронным, и синхронная задача console.log должна выполняться первой, поэтому наш вывод таков:

exec console!
task()

Иди и убедись, что результат правильный! Затем изменим предыдущий код:

setTimeout(() => {
    task()
},3000)
sleep(10000000)

На первый взгляд, это выглядит почти так же, но когда мы выполняем этот код в Chrome, мы обнаруживаем, что консоль выполняет task () гораздо дольше, чем 3 секунды. Согласованная задержка в три секунды, почему сейчас так долго? На данный момент нам нужно заново понять определение setTimeout. Начнем с того, как выполняется приведенный выше код:

  • Task () входит в Event Table и регистрируется, и начинается отсчет времени.
  • Выполните функцию sleep, очень медленно, очень медленно, отсчет времени все еще продолжается.
  • Через 3 секунды тайм-аут события синхронизации завершается, и task () входит в Event Queue, но sleep слишком медленно завершается, поэтому вам нужно подождать.
  • Выполнение sleep, наконец, завершено, и task ()finally переходит к выполнению основного потока из Event Queue.

После описанного выше процесса мы знаем, что функция setTimeout добавляет задачу для выполнения (в данном случае task ()) к Event Queue по истечении указанного времени, и поскольку однопоточная задача должна выполняться одна за другой, если предыдущая задача занимает слишком много времени, то мы можем только ждать, в результате чего реальное время задержки намного превышает 3 секунды. Мы также часто сталкиваемся с таким кодом, как setTimeout (fn,0). Что значит выполнить за 0 секунд? Можно ли его провести сразу? Ответ - нет. SetTimeout (fn,0) означает указать самое раннее время простоя, доступное основному потоку для выполнения задачи, что означает, что вам не нужно больше ждать секунд, как только стек станет пустым, как только все синхронные задачи в основном потоке будут выполнены. стек завершен. Примеры следующие:

//code 1
console.log('exeu 1');
setTimeout(() => {
    console.log('exeu 2')
},0);
//code 2
console.log('exeu 1');
setTimeout(() => {
    console.log('exeu 2')
},3000);

Вывод кода 1:

exeu 1
exeu 2

Вывод кода 2:

exeu 1
//3s later
exeu 2

Одна вещь, которую следует добавить о setTimeout, заключается в том, что даже если основной поток пуст, 0 миллисекунд на самом деле недостижим. Согласно стандартам HTML, минимум составляет 4 миллисекунды. Кому интересно, разберутся сами.

4. отношения любви-ненависти с setInterval

Когда дело доходит до setTimeout, вы, конечно же, не можете не заметить его брата-близнеца setInterval. Они похожи, за исключением того, что последний является выполнением цикла. Для порядка выполнения setInterval помещает зарегистрированные функции в Event Queue с заданными интервалами, и если предыдущая задача занимает слишком много времени, вам также нужно подождать. Единственное, что нужно отметить, это то, что для setInterval (fn,ms) мы уже знаем, что fn не выполняется каждую мс секунду, но каждую мс секунду fn входит в Event Queue. Как только время выполнения функции обратного вызова setInterval fn превышает время задержки ms, временной интервал вообще отсутствует. Пожалуйста, внимательно прочувствуйте это предложение.

5. Promise и process.nextTick(обратный вызов)

Мы изучили традиционный таймер, а затем изучим производительность Promise и process.nextTick (callback). Process.nextTick (callback) похож на версию setTimeout для node.js, вызывая функцию обратного вызова в следующем цикле цикла событий. Давайте приступим к делу. Помимо синхронных и асинхронных задач в широком смысле, у нас есть более тонкое определение задач:

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

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

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

new Promise(function(resolve) {
    console.log('promise');
}).then(function() {
    console.log('then');
})

console.log('console');
  • Этот код входит в основной поток как задача макроса. Если вы столкнулись с setTimeout первым, зарегистрируйте его функцию обратного вызова и распространите ее на задачу макроса Event Queue. (процесс регистрации такой же, как описано выше, что не описано ниже).
  • Далее выполняется Promise, сразу же выполняется new Promise, а функция then распределяется по микрозадаче Event Queue.
  • Если вы встретите console.log (), немедленно выполните его.
  • Хорошо, общий сценарий кода заканчивается первой макрозадачей, так что же такое микрозадачи? Мы обнаружили, что then находится в микрозадаче Event Queue, выполняется.
  • Итак, первый раунд цикла событий закончен, и мы начинаем второй раунд, естественно, начиная с макро-задачи Event Queue. Мы нашли callback-функцию, соответствующую setTimeout, в макрозадаче Event Queue и тут же ее выполнили.
  • Конец.

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

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})
setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

Анализ первого раунда процесса цикла событий выглядит следующим образом:

  • Общий сценарий входит в основной поток как первая задача макроса, сталкивается с console.log и выводит 1.
  • При обнаружении setTimeout его функция обратного вызова распространяется на задачу макроса Event Queue. Давайте пока назовем его setTimeout1.
  • Когда встречается process.nextTick (), его функция обратного вызова распространяется на очередь событий микрозадачи. Запишем его как process1.
  • Если вы столкнетесь с Promise, new Promise, вы выполните его напрямую и выведете 7. Затем он будет распределен в очередь событий микрозадачи. Запишем это как then1.
  • Также встречается SetTimeout, и его функция обратного вызова распространяется на макрозадачу Event Queue, которую мы называем setTimeout2.

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

  • Мы нашли две микрозадачи, process1 и then1.
  • Выполнить процесс 1 и вывести 6.
  • Выполните then1 и выведите 8.

Итак, первый раунд событий официально завершен, и результатом этого раунда является вывод 1, 7, 6, 8. Итак, второй раунд временного цикла начинается с макрозадачи setTimeout1:

  • Первый вывод 2. Далее вы сталкиваетесь с process.nextTick (), который также распространяется на очередь событий микрозадачи, помеченную как process2. New Promise немедленно выполняет вывод 4, а затем распределяет его по микрозадаче Event Queue, которая отмечена then2.

  • Когда второй раунд макрозадачи цикла событий заканчивается, мы обнаруживаем, что есть две микрозадачи, process2 и then2, которые можно выполнить.
  • Выход 3.
  • Выход 5.
  • Второй раунд цикла событий завершен, и выход второго раунда 2, 4, 3, 5.
  • Начинается третий раунд цикла событий, и остается выполнить только setTimeout2.
  • Прямой вывод 9.
  • Распределите process.nextTick () в очередь событий микрозадачи. Запишите его как процесс3.
  • Выполните новый Promise напрямую и выведите 11.
  • Распределите затем в очередь событий микрозадачи, помеченную как then3.

  • Третий раунд выполнения макрозадач цикла событий завершается выполнением двух микрозадач, process3 и then3.
  • Выход 10.
  • Выход 12.
  • Третий раунд цикла событий заканчивается, и третий раунд выводит 9, 11, 10, 12.

Весь код, всего три цикла событий, полный вывод: 1, 7, 6, 8, 2, 4, 3, 5, 9, 11, 10, 12. (обратите внимание, что libuv зависимости мониторинга событий в среда узла не совсем такая же, как интерфейсная среда, и порядок вывода может быть неправильным.)

6. Пишите в конце

(1). Асинхронность js. Мы с самого начала говорили, что javascript — это однопоточный язык. Независимо от того, какой новый фреймворк и новый синтаксический сахар реализуют так называемый асинхронизм, он фактически моделируется синхронными методами. Очень важно твердо усвоить однопоточность.

(2). Цикл событий. Цикл событий — это способ реализации асинхронности в js, а также механизм выполнения js.

(3). Выполнение и работа javascript. Есть большая разница между исполнением и бегом. Javascript выполняется по-разному в разных средах, таких как узел, браузер, Ringo и т.д. И запуск в основном относится к движку синтаксического анализа javascript, который является унифицированным.

(4). УстановитьНемедленно. Есть много видов микро-задач и макро-задач, таких как setImmediate и т. д., в выполнении есть что-то общее, заинтересованные студенты могут понять сами.

(5). Последний.

  • Javascript — это однопоточный язык.
  • Цикл событий — это механизм выполнения javascript.

Твердо усвойте два основных момента, серьезно изучите javascript как центр и осуществите великую мечту стать мастером интерфейса как можно скорее!