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

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

JavaScript - это интерпретируемый язык, или в Chrome он теперь известен как своевременная компиляция. Хорошо, отлично, я заблудился, Что !!!! Думаю, именно здесь мы теряем большинство людей. Мы ожидаем, что люди просто знают, что означает интерпретируемый язык. По сути, это означает, что он читается построчно, сверху вниз. Примерно так же, как вы читаете этот пост в блоге, по крайней мере, я надеюсь, что вы читаете, строка за строкой, сверху вниз. JavaScript является однопоточным, что означает, что он выполняет одно действие за раз. Он работает синхронно, то есть одно за другим.

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

const num1 = 2;
const num2 = 4;
function multiplyBy2(inputNumber){
    const result = inputNumber * 2;
    return result;
}

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

Мы начинаем с самого начала и говорим сохранить 2 в переменной с именем num1. Затем мы переходим к следующей строке и говорим сохранить 4 в переменной с именем num2. Хорошо, пока все просто, правда? Затем мы переходим к функции, в которой сохраняем метку multiplyBy2 и связываем ее с определением функции. Обычно мы храним всю функцию в метке multiplyBy2.

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

Global Memory:
num1: 2
num2: 4
multiplyBy2: function // includes everything the function does

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

Теперь давайте посмотрим на этот код:

const num1 = 2;
const num2 = 4;
function multiplyBy2(inputNumber){
    const result = inputNumber * 2;
    return result;
}
const output = multiplyBy2(num1);
const newOutput = multiplyBy2(num2);

Как и раньше, мы сохраняем num1, num2 и multiplyBy2 в глобальной памяти. Мы не заглядываем внутрь функции, мы спускаемся вниз и читаем следующую строку, в которой говорится, что сохранить то, что возвращается из функции multiplyBy2 в переменной output. JavaScript любит, чтобы у всего было значение, и, поскольку он еще не знает, что будет возвращено из функции, он еще не начал входить в него, поэтому он сохраняет undefined по умолчанию. значение output, ожидая фактического значения. Вывод не интересует функция multiplyBy2, его интересует только то, что стоит после ключевого слова return, поскольку это то, что будет сохранено в output . Итак, мы должны проделать работу и посмотреть, какое значение мы собираемся получить от функции, чтобы присвоить ее выходу.

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

function multiplyBy2(inputNumber){
    const result = inputNumber * 2;
    return result;
}

Итак, глядя на нашу функцию, мы берем параметр inputNumber и присваиваем ему значение 2 (это то, что было сохранено в переменной num1). Мы сохраняем это в локальной памяти, а не в глобальной. Локальная память известна как среда переменных, потому что она похожа на среду доступных переменных, доступных переменных вокруг вас или доступных данных. Затем мы присваиваем вычисленный результат inputNumber * 2, (2 * 2), который равен 4, переменной result. Что нам делать с результатом? Мы его возвращаем. Это означает, что он возвращается и будет сохранен в переменной output в глобальной памяти, заменив значение по умолчанию undefined.

output = multiplyBy2(2)
Local Memory
inputNumber: 2
result: 4
Because of the return keyword this is passed to the global memory
output: 4

Что происходит с контекстом выполнения? Мы завершили вызов функции multiplyBy2, поэтому контекст выполнения был стерт, за исключением возвращенного значения, которое сохраняется в output. Итак, к какому контексту выполнения мы вернемся? Глобальный контекст выполнения. По умолчанию JavaScript этого не знает. Необходимо как-то отслеживать этот факт, мы называем это стеком вызовов.

Стек вызовов

Стек вызовов - это специальная структура данных, которая позволяет нам отслеживать, где поток выполнения находится в вашем коде. В глобальном исполнении или в локальном? Первый контекст выполнения - глобальный, поэтому мы добавляем его первым. То, что находится наверху стека вызовов, - это то, где находится поток JavaScript или где он в настоящее время выполняет ваш код. Все, что находится наверху стека вызовов, - это то место, где вы находитесь. Последнее, что вы кладете, - это первое, что вы вынимаете. Это стек функций, поэтому функция добавляется в стек вызовов, мы называем это отправкой в ​​стек вызовов, а когда она завершается, она удаляется из стека вызовов. Последний вошел - первым ушел. Мы знаем, когда мы закончили работу с функцией, благодаря ключевому слову return или конечной фигурной скобке. Помните, что если вы не укажете в своей функции слово return, то по умолчанию будет установлено значение undefined. Как только функция будет выполнена, она будет удалена из стека вызовов, известная как всплывающее окно. Затем JavaScript просматривает стек вызовов и видит, что последний является глобальным, поэтому возвращается в глобальный контекст выполнения.

Следующая строка - const newOutput = multiplyBy2 (num2); мы добавляем значение по умолчанию undefined в newOutput в нашей глобальной памяти, а затем JavaScript говорит, что нужно развернуть новый контекст выполнения, потому что мы вызвали функцию, используя круглые скобки, и этот новый локальный контекст добавляется в начало нашего стек вызовов. Мы повторяем то, что делали выше, сохраняя возвращаемое значение в newOutput, которое хранится в глобальной памяти. Затем этот локальный контекст удаляется из стека вызовов, и мы возвращаемся к глобальному контексту выполнения. Теперь у нас есть:

Global Memory:
num1: 2
num2: 4
multiplyBy2: function // includes everything the function does
output: 4
newOutput: 8

Здесь следует помнить, что когда JavaScript добрался до последней строки, const newOutput = multiplyBy2 (num2); он не подпрыгивал, чтобы попытаться вычислить результат функции. Помните, что JavaScript читается построчно сверху вниз. Когда он доходит до последней строки, ему не нужно перепрыгивать, он просто просматривает глобальную память, где мы сохранили эту функцию с меткой под названием multiplyBy2. В нем есть вся информация, необходимая для запуска этой функции, поэтому он просто использует то, что хранится в его памяти.

Видите ли, как только вы поймете, как работает JavaScript, понимание кода станет намного проще.

Резюмируем:

  1. JavaScript - это интерпретируемый язык, он читается построчно, сверху вниз.
  2. JavaScript является однопоточным (по одному), синхронное выполнение.
  3. Как только мы запускаем наш код, мы создаем глобальный контекст выполнения.
  4. Когда вы выполняете функцию, вы создаете новый локальный контекст выполнения.
  5. Поток выполнения - это когда мы разбираем и выполняем код строка за строкой.
  6. В локальной памяти («Переменная среда») все, что определено в функции, хранится, а не в глобальной памяти.
  7. Запуск / вызов / вызов функции - это не то же самое, что определение функции. Его определение указывает на то, что он делает, тогда как его запуск означает зайти внутрь и запустить код.
  8. Стек вызовов отслеживает, в каком контексте выполнения мы находимся, то есть какая функция в настоящее время выполняется и куда вернуться после того, как контекст выполнения будет извлечен из стека.

Спасибо Will Sentance и его удивительный курс по Frontend Masters, взятый из - JavaScript: The Hard Parts (Принципы JavaScript), который я очень рекомендую.