[JS Pocket Book] Глава 4: Основной принцип работы движка JS

JavaScript ECMAScript 6

Добавить Автора

Переводчик: Front-end Xiaozhi

Источник: гитхаб


Все говорили, что нет проекта для написания резюме, поэтому я помог вам найти проект, и это было с бонусом.【Учебник по строительству】.

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

Откройте консоль браузера в Chrome и посмотритеSourcesВ этой колонке справа вы можете перейти кCall StackКоробка.

Движок JS — это мощный компонент, который может компилировать и интерпретировать наш код JS. Наиболее популярными движками JS являются V8, используемые Google Chrome и Node.js, SpiderMonkey для Firefox и JavaScriptCore для Safari/WebKit.

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

Одним из компонентов являетсяСтек вызовов, запускает наш код с глобальной памятью и контекстом выполнения.

Движок Js и глобальная память (Global Memory)

JavaScript — это компилируемый язык и интерпретируемый язык. Хотите верьте, хотите нет, но движку JS требуется всего несколько микросекунд для компиляции кода перед его выполнением.

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

Рассмотрим следующий код:

var num = 2;
function pow(num) {
    return num * num;
}

Если вас спросят, как вы обрабатываете приведенный выше код в браузере?Что бы вы сказали? Вы можете сказать «браузер читает код» или «браузер выполняет код».

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

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

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

В приведенном выше примере ничего не делается, но что, если мы запустим функцию следующим образом:

var num = 2;
function pow(num) {
    return num * num;
}
pow(num);

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

JS-движки: как они работают, глобальный контекст выполнения и стек вызовов

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

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

Стек вызовов — это структура данных стека: это означает, что элементы могут входить сверху, но если над ними есть какие-то элементы, они не могут уйти, JS-функции такие.

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

Возвращаясь к нашему примеру, когда вызывается функция, JS-движок помещает функцию в стек вызовов.

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

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

Даже с таким простым изменением, как следующее, движок JS создастлокальный контекст выполнения:

var num = 2;
function pow(num) {
    var fixed = 89;
    return num * num;
}
pow(num);

Обратите внимание, что яpowФункция, называемаяfixedПеременные. В этом случае в функции pow создается локальный контекст выполнения,fixedвставляется переменнаяpowв локальном контексте выполнения функции.

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

JavaScript однопоточный и другие интересные истории

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

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

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

Когда браузер загружает некоторый код JS, движок JS считывает его построчно и выполняет следующие шаги:

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

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

Асинхронный JS, очереди обратного вызова и циклы событий

Глобальная память (куча), контекст выполнения и стек вызовов объясняют, как синхронный код JS работает в браузере. Однако мы что-то упускаем, что происходит, когда выполняются некоторые асинхронные функции?

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

Когда мы запускаем асинхронную функцию, браузер принимает функцию и запускает ее. Рассмотрим следующий код:

setTimeout(callback, 10000);
function callback(){
    console.log('hello timer!');
}

setTimeoutВсе знают, что его использовали много раз, но вы можете не знать, что это не так.Встроенные JS-функции.. То есть, когда появился JS, не было встроенногоsetTimeout.

setTimeoutAPI браузера(Browser API), который представляет собой набор удобных инструментов, которые браузеры предоставляют нам бесплатно. Что это означает на практике?setTimeoutэто API для браузера, функция запускается непосредственно браузером (ненадолго появится в стеке вызовов, но тут же будет удалена).

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

var num = 2;
function pow(num) {
    return num * num;
}
pow(num);
setTimeout(callback, 10000);
function callback(){
    console.log('hello timer!');
}

Схематическая диаграмма выглядит следующим образом:

Как вы видете,setTimeoutЗапускается в контексте браузера. Через 10 секунд срабатывает таймер, и функция обратного вызова готова к работе. но сначала это должно пройтиОчередь обратного вызова). Очередь обратного вызова — это структура данных очереди, а очередь обратного вызова — упорядоченная очередь функций.

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

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

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

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

Callback Hell и Promises в ES6

Обратные вызовы есть везде в JS, они используются как в синхронном, так и в асинхронном коде. Рассмотрим следующееmapметод:

function mapper(element){
    return element * 2;
}
[1, 2, 3, 4, 5].map(mapper);

mapperэтоmapФункция обратного вызова передается внутри. Приведенный выше код является синхронным, рассмотрим асинхронный случай:

function runMeEvery(){
    console.log('Ran!');
}
setInterval(runMeEvery, 5000);

Код асинхронный, и мы находимся вsetIntervalпередать обратный вызов вrunMeEvery. Обратные вызовы распространены в JS повсеместно, поэтому есть проблема:ад обратного звонка.

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

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

Ад обратного звонка здесь обсуждаться не будет, если интересно, есть сайт,callbackhell.com, в котором более подробно рассматривается проблема и предлагаются некоторые решения.

Сейчас мы сосредоточимся на ES6.Promises. Промисы ES6 — это дополнение к языку JS, предназначенное для решения ужасного ада обратных вызовов. Но что такое обещания?

JS Promises — это представления будущих событий. Обещания могут закончиться успехом: на жаргоне мы решили эту проблемуresolved(fulfilled). Но если Обещание не выполняется, мы говорим, что оно ** отклонено.условие. Промисы также имеют состояние по умолчанию: каждое новое промис начинается сНачинается состояние ожидания (в ожидании)**.

Создание и использование промисов JavaScript

Чтобы создать новый Promise, вызовите конструктор Promise, передав функцию обратного вызова. Функция обратного вызова может принимать два параметра:resolveа такжеreject. Следующим образом:

const myPromise = new Promise(function(resolve){
    setTimeout(function(){
        resolve()
    }, 5000)
});

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

const myPromise = new Promise(function(resolve, reject){
    setTimeout(function(){
        reject()
    }, 5000)
});

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

// 不能忽略 resolve !
const myPromise = new Promise(function(reject){
    setTimeout(function(){
        reject()
    }, 5000)
});

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

const myPromise = new Promise(function(resolve) {
  resolve([{ name: "Chris" }]);
});

Но мы по-прежнему не видим никаких данных. отPromiseЧтобы извлечь данные, вам нужно связать файл с именемthenМетоды. Для получения фактических данных требуется обратный вызов:

const myPromise = new Promise(function(resolve, reject) {
  resolve([{ name: "Chris" }]);
});
myPromise.then(function(data) {
    console.log(data);
});

Обработка ошибок для промисов

Для синхронного кода обработка ошибок JS в основном проста, например:

function makeAnError() {
  throw Error("Sorry mate!");
}
try {
  makeAnError();
} catch (error) {
  console.log("Catching the error! " + error);
}

выведет:

Catching the error! Error: Sorry mate!

Теперь попробуйте использовать асинхронную функцию:

function makeAnError() {
  throw Error("Sorry mate!");
}
try {
  setTimeout(makeAnError, 5000);
} catch (error) {
  console.log("Catching the error! " + error);

из-заsetTimeout, приведенный выше код является асинхронным, посмотрите, что происходит при запуске:

  throw Error("Sorry mate!");
  ^
Error: Sorry mate!
    at Timeout.makeAnError [as _onTimeout] (/home/valentino/Code/piccolo-javascript/async.js:2:9)

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

это потому, чтоtry/catchПрименяется только к синхронному коду. если тебе интересно, Обработка ошибок в Node.jsЭтот вопрос будет подробно разъяснен.

К счастью, у промисов есть способ обрабатывать асинхронные ошибки, как если бы они были синхронными:

const myPromise = new Promise(function(resolve, reject) {
  reject('Errored, sorry!');
});

В приведенном выше примере мы можем использоватьcatchОбработчик обрабатывает ошибку:

const myPromise = new Promise(function(resolve, reject) {
  reject('Errored, sorry!');
});
myPromise.catch(err => console.log(err));

Мы также можем вызвать Promise.reject() для создания и отклонения обещания.

Promise.reject({msg: 'Rejected!'}).catch(err => console.log(err));

Комбинация промисов: Promise.all, Promise.allSettled, Promise.any

Promise API предоставляет множество способов объединения промисов. Наиболее полезными из них являютсяPromise.all, который принимает массив промисов и возвращает промис. Если одно из промисов в параметре терпит неудачу (отклоняется), этот обратный вызов экземпляра завершается сбоем (отклоняется), и причиной сбоя является результат первого невыполненного обещания.

Promise.race(iterable)Метод возвращает обещание, как только один из итераторовpromiseразрешено или отклонено, возвращеноpromiseбудут решены или отклонены.

В более новых версиях V8 также будут реализованы две новые комбинации:Promise.allSettledа такжеPromise.any.Promise.anyПредложение все еще находится на ранних стадиях: на момент написания еще нет браузеров, поддерживающих его.

Promise.anyможет указать, является ли какое-либо обещаниеfullfilled. а такжеPromise.raceРазница в том,на Promise.anyне откажется ни от одного из нихPromiseбыть отвергнутым.

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

Асинхронная эволюция: от промисов к асинхронному/ожиданию

Появление ECMAScript 2017 (ES8), введение нового синтаксиса и рождение async/await.

async/await — это просто синтаксический сахар Promise. Это просто новый способ написания асинхронного кода на основе промисов.async/awaitНикоим образом не изменяет JS, имейте в виду, что JS должен быть обратно совместим со старыми браузерами и не должен нарушать существующий код.

Вот пример:

const myPromise = new Promise(function(resolve, reject) {
  resolve([{ name: "Chris" }]);
});
myPromise.then((data) => console.log(data))

использоватьasync/await, мы можем обернуть промис тегом, помеченным какasyncфункция, а затем дождитесь возврата результата:

const myPromise = new Promise(function(resolve, reject) {
  resolve([{ name: "Chris" }]);
});
async function getData() {
  const data = await myPromise;
  console.log(data);
}
getData();

Интересно,asyncФункции также возвращают промисы, вы тоже можете это сделать:

async function getData() {
  const data = await myPromise;
  return data;
}
getData().then(data => console.log(data));

Как тогда обрабатывать ошибки?async/awaitОдним из преимуществ является то, что вы можете использоватьtry/catch. Снова взглянув на промисы, мы используемcatchОбработчик для обработки ошибок:

const myPromise = new Promise(function(resolve, reject) {
  reject('Errored, sorry!');
});
myPromise.catch(err => console.log(err));

использоватьasyncфункцию, мы можем реорганизовать приведенный выше код:

async function getData() {
  try {
    const data = await myPromise;
    console.log(data);
    // or return the data with return data
  } catch (error) {
    console.log(error);
  }
}
getData();

Не всем нравится этот стиль.try/catchДелает код подробным при использованииtry/catch, есть еще одна причуда, на которую следует обратить внимание:

async function getData() {
  try {
    if (true) {
      throw Error("Catch me if you can");
    }
  } catch (err) {
    console.log(err.message);
  }
}
getData()
  .then(() => console.log("I will run no matter what!"))
  .catch(() => console.log("Catching err"));

результат операции:

Обе приведенные выше строки будут напечатаны. пожалуйста, помните,try/catch — это синхронная конструкция, но наша асинхронная функция возвращает Promise.Они едут по двум разным путям, как два поезда. Но они никогда не встречаются, то есть ошибка, выдаваемая throw, никогда не вызывает getData().catchметод.

На практике мы не хотимthrowтрогатьthenобработчик. Одним из решений является возврат из функцииPromise.reject():

async function getData() {
  try {
    if (true) {
      return Promise.reject("Catch me if you can");
    }
  } catch (err) {
    console.log(err.message);
  }
}

Ошибки теперь обрабатываются должным образом

getData()
  .then(() => console.log("I will NOT run no matter what!"))
  .catch(() => console.log("Catching err"));
"Catching err" // 输出

Помимо этого, async/await кажется лучшим способом создания асинхронного кода в JS.. У нас больше контроля над обработкой ошибок, и код выглядит чище.

Суммировать

JS — это язык сценариев для Интернета, который компилируется, а затем интерпретируется движком. Среди самых популярных движков JS — V8, используемый Google Chrome и Node.js, а также созданный Firefox.SpiderMonkey, и тот, который используется SafariJavaScriptCore.

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

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

Чтобы упростить поток асинхронного кода,ECMAScript 2015 приносит нам обещания. Обещание — это асинхронный объект, используемый для представления неудачи или успеха любой асинхронной операции. Но улучшения на этом не остановились.В 2017 году родился async/await.:этоPromiseСтиль оформления, позволяющий писать асинхронный код так, как если бы он был синхронным.

думать

  • Как браузеры понимают код JS?
  • Какова основная задача стека вызовов?
  • Можете ли вы описать основные компоненты движка JS и как они работают вместе?
  • Что делает очередь микрозадач?
  • Что такое обещания?
  • Можно ли обрабатывать ошибки в асинхронном коде? Если да, то как?
  • Можете ли вы назвать несколько методов API браузера

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

оригинал:GitHub.com/Валентино Г.А.…

общаться с

Статья постоянно обновляется каждую неделю. Вы можете выполнить поиск «Big Move to the World» в WeChat, чтобы прочитать и обновить ее как можно скорее (на одну или две статьи раньше, чем в блоге). Эта статья находится на GitHub.GitHub.com/QQ449245884…Он был включен, и многие мои документы были разобраны. Добро пожаловать в Звезду и совершенство. Вы можете обратиться в тестовый центр для ознакомления во время собеседования. Кроме того, обратите внимание на паблик-аккаунт и ответьте в фоновом режиме.Благосостояние, вы можете увидеть преимущества, вы знаете.