На этот раз досконально изучите механизм выполнения JavaScript.

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

Цель этой статьи - убедиться, что вы полностью понимаете механизм выполнения javascript.Если вы все еще не понимаете после прочтения этой статьи, вы можете побить меня.

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

  • javascript выполняется в том порядке, в котором появляются операторы

Увидев это, читатель побьет людей: разве я не знаю, что js выполняется построчно? Тебе еще нужно сказать? Не волнуйтесь, потому что js выполняется построчно, поэтому мы думаем, что js выглядит так:

let a = '1';
console.log(a);

let b = '2';
console.log(b);

Однако на самом деле js выглядит так:

setTimeout(function(){
    console.log('定时器开始啦')
});

new Promise(function(resolve){
    console.log('马上执行for循环啦');
    for(var i = 0; i < 10000; i++){
        i == 99 && resolve();
    }
}).then(function(){
    console.log('执行then函数啦')
});

console.log('代码执行结束');

согласно сjs выполняется в том порядке, в котором появляются операторыС этой идеей я уверенно записываю вывод:

//"定时器开始啦"
//"马上执行for循环啦"
//"执行then函数啦"
//"代码执行结束"

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

Нам действительно нужно досконально понять механизм выполнения javascript.

1. О JavaScript

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

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

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

  • Синхронизировать задачу
  • асинхронная задача

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

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

  • Синхронные и асинхронные задачи попадают в разные «места» выполнения, синхронно входят в основной поток, асинхронно входят в Таблицу событий и регистрируют функции.
  • Когда указанное действие будет выполнено, таблица событий переместит эту функцию в очередь событий.
  • После того, как задача в основном потоке опустеет, она отправится в Очередь событий, чтобы прочитать соответствующую функцию и войти в основной поток для выполнения.
  • Вышеупомянутый процесс будет продолжать повторяться, что часто называют циклом событий.

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

Сказав так много текста, проще направить кусок кода:

let data = [];
$.ajax({
    url:www.javascript.com,
    data:data,
    success:() => {
        console.log('发送成功!');
    }
})
console.log('代码执行结束');

Вышеупомянутое простоajaxКод заявки:

  • ajax входит в таблицу событий и регистрирует функцию обратного вызоваsuccess.
  • воплощать в жизньconsole.log('代码执行结束').
  • Событие ajax завершено, функция обратного вызоваsuccessВойдите в очередь событий.
  • Основной поток считывает функцию обратного вызова из очереди событий.successи выполнить.

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

3. Любовь-ненависть setTimeout

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

setTimeout(() => {
    console.log('延时3秒');
},3000)

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

Давайте посмотрим на пример:

setTimeout(() => {
    task();
},3000)
console.log('执行console');

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

//执行console
//task()

Проверьте, результат правильный! Затем мы модифицируем предыдущий код:

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

sleep(10000000)

На первый взгляд почти то же самое, но когда мы выполняем этот код в хроме, мы обнаруживаем, что консоль выполняетtask()Требуемое время намного больше, чем 3 секунды.Обещанная задержка 3 секунды.Почему сейчас так долго?

В это время нам нужно переосмыслитьsetTimeoutОпределение. Давайте сначала поговорим о том, как выполняется приведенный выше код:

  • task()Войдите в таблицу событий и зарегистрируйтесь, время начинается.
  • воплощать в жизньsleepфункция, очень медленно, очень медленно, время продолжается.
  • 3 секунды истекли, событие по времениtimeoutЗаканчивать,task()в очередь событий, ноsleepЭто слишком медленно, это еще не закончено, так что я должен ждать.
  • sleepнаконец закончено,task()Наконец-то вошел в выполнение основного потока из очереди событий.

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

Мы также часто сталкиваемсяsetTimeout(fn,0)Что значит выполнить такой код через 0 секунд? Можно ли его выполнить немедленно?

Ответ - нет,setTimeout(fn,0)Смысл в том, чтобы указать, что задача выполняется в самое раннее доступное время простоя основного потока, а это означает, что нет необходимости ждать, сколько секунд, пока задачи синхронизации в стеке выполнения основного потока находятся все выполнено, стек будет выполнен немедленно. Например:

//代码1
console.log('先执行这里');
setTimeout(() => {
    console.log('执行啦')
},0);
//代码2
console.log('先执行这里');
setTimeout(() => {
    console.log('执行啦')
},3000);  

Вывод кода 1:

//先执行这里
//执行啦

Вывод кода 2:

//先执行这里
// ... 3s later
// 执行啦

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

4. Ненавижу и люблю setInterval

вышеизложенное закончилосьsetTimeout, конечно, чтобы не упустить своего брата-близнецаsetInterval. Они похожи, за исключением того, что последний является выполнением цикла. Для исполнительного листа,setIntervalЗарегистрированная функция будет помещаться в Очередь событий каждое указанное время.Если предыдущая задача занимает слишком много времени, ее также нужно подождать.

Единственное, что следует отметить, это то, что дляsetInterval(fn,ms)Например, мы уже знаем, что не каждыйmsбудет выполняться один раз в секундыfn, но каждый разmsсекунды, будетfnВойдите в очередь событий. однаждыsetIntervalфункция обратного вызоваfnВремя выполнения превысило время задержкиms, то разрыва во времени нет вообще.. Читателей просят внимательно смаковать это предложение.

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

Мы уже изучили традиционный таймер, а затем изучимPromiseа такжеprocess.nextTick(callback)Представление.

PromiseОпределение и функцииPromise. а также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Функция отправляется в очередь событий микрозадачи.
  • встретитьconsole.log(), выполнить немедленно.
  • Итак, в качестве первой макрозадачи выполняется весь скрипт кода, посмотрим, какие там микрозадачи? мы нашлиthenВ очереди событий микрозадачи выполните.
  • ок, первый виток цикла событий закончен, начинаем второй виток цикла, естественно, начиная с макрозадачи Event Queue. Мы нашли макро-задачу Event Queue вsetTimeoutСоответствующая функция обратного вызова выполняется немедленно.
  • конец.

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

Давайте проанализируем более сложный фрагмент кода, чтобы увидеть, действительно ли вы владеете механизмом выполнения 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.thenРаспространяется на очередь событий микрозадачи. мы записываем какthen1.
  • встретились сноваsetTimeout, его callback-функция отправляется в макрозадачу Event Queue, мы записываем ее какsetTimeout2.
Задача макроса Очередь событий Очередь событий микрозадач
setTimeout1 process1
setTimeout2 then1
  • В приведенной выше таблице показано состояние каждой очереди событий в конце первого раунда макрозадач цикла событий, и в это время были выведены 1 и 7.

  • мы нашлиprocess1а такжеthen1Две микрозадачи.

  • воплощать в жизньprocess1, вывод 6.

  • воплощать в жизньthen1, выход 8.

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

  • Первый выход 2. встретились дальшеprocess.nextTick(), а также распределять его в Очередь событий микрозадачи, обозначаемую какprocess2.new PromiseНемедленно выполнить выход 4,thenТакже распространяется на очередь событий микрозадач, записанная какthen2.
Задача макроса Очередь событий Очередь событий микрозадач
setTimeout2 process2
then2
  • В конце второго раунда макрозадач цикла событий мы обнаружили, что естьprocess2а такжеthen2Можно выполнить две микрозадачи.
  • вывод 3.
  • вывод 5.
  • Второй раунд цикла событий заканчивается, и второй раунд выводит 2, 4, 3, 5.
  • Начинается третий раунд цикла событий, и в это время остается только setTimeout2, и он выполняется.
  • Выведите 9 напрямую.
  • Будуprocess.nextTick()Распространяется на очередь событий микрозадачи. отмечен какprocess3.
  • прямое исполнениеnew Promise, выход 11.
  • БудуthenРаспространяется на очередь событий микрозадач, записывается какthen3.
Задача макроса Очередь событий Очередь событий микрозадач
process3
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 выполняется по-разному в разных средах, таких как node, браузер, Ringo и т. д. Операция в основном относится к унифицированному механизму синтаксического анализа javascript.

(4)setImmediate

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

(5) Последний из последних

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

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