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

Функции помогают нам использовать один из фундаментальных принципов проектирования программного обеспечения — СУХОЙ (не повторяйтесь).

В JS функции — это «объекты первого класса». Это означает, что все, что есть у объектов, есть и у них.

Выполнение функции называется ее вызовом, вызовом или применением.

Параметры

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

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

Мы можем определить параметры по умолчанию при объявлении функции.

Подробнее:

Контекст выполнения

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

  • Создайте глобальный объект, т. е. window в веб-браузере или global в Node.js.
  • Создайте объект this и привяжите его к глобальному объекту.
  • Настройте кучу памяти для хранения переменных и ссылок на функции.
  • Сохраните объявления функций в куче памяти и переменных в глобальном контексте выполнения с начальными значениями как undefined.

Для каждого вызова функции с этого момента механизм JavaScript создает новый контекст выполнения функции, включающий:

  • Поток выполнения (проходим код в функции построчно)
  • Локальная память, в которой хранится все, что определено в функции.

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

Мы можем думать, что «контекст» означает пространство (ящик), в котором выполняется код. См. пример ниже.

Стек вызовов

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

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

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

Примечания от MDN:

  • Когда сценарий вызывает функцию, интерпретатор добавляет ее в стек вызовов, а затем начинает выполнять функцию.
  • Любые функции, вызываемые этой функцией, добавляются в стек вызовов выше и выполняются там, где достигаются их вызовы.
  • Когда текущая функция завершена, интерпретатор удаляет ее из стека и возобновляет выполнение с того места, где оно было остановлено в последнем листинге кода.
  • Если стек занимает больше места, чем ему отведено, это приводит к ошибке «переполнение стека»: RangeError: Maximum call stack size exceeded

Подробнее:

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

В нашем коде мы создаем функцию с именем multiply, а затем создаем переменную с именем twoByTwo, которая является результатом вызова multiply(2, 2).

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

Когда мы запускаем наш код, JavaScript Engine добавляет «глобальную» функцию в стек вызовов, которая создает глобальный контекст выполнения. Внутри этого глобального контекста выполнения создается локальная память.

Затем он построчно анализирует наш код и объявляет переменную multiply в глобальной памяти (ПРИМЕЧАНИЕ: мы не знаем, что внутри этой функции, пока не вызовем ее, это также означает, что JavaScript не войдет внутрь и анализирует эту функцию до тех пор, пока мы не вызовем эту функцию). Затем он переходит к следующей строке, где мы объявляем переменную twoByTwo, и значение этой переменной является результатом multiply(2,2) (ПРИМЕЧАНИЕ: Пока эта функция, выполняющая наше twoByTow, не имеет никакого значения, мы никогда не заметьте это, но это то, что происходит на самом деле).

Теперь JavaScript смотрит на Scope Memory (это глобальная память) и пытается найти multiply, а это функция. Таким образом, он вызовет ее (ПРИМЕЧАНИЕ: если наш multiply ссылается не на функцию, JavaScript выдаст ошибку: multiply is not a function).

После этого наша функция добавляется в стек вызовов и создается локальный контекст выполнения для multiply(2,2). Внутри этого контекста также создается локальная память, и JavaScript начинает синтаксический анализ кода внутри функции multiply. Он объявляет переменную result внутри локальной памяти со значением 2*2, и это 4, затем наша функция return result. Здесь JavaScript также просматривает область памяти (это локальная память) и пытается найти result (ПРИМЕЧАНИЕ: если JavaScript не найдет result в локальной памяти, он попытается найти ее в родительской области памяти (это глобальная память). ) и если нет result, JavaScript выдаст ошибку: is not defined). В нашем случае JavaScript находит его в Local Scope и просто возвращает.

Затем JavaScript Engine удаляет multiply(2,2) из стека вызовов, а локальный контекст выполнения для multiply(2,2) автоматически уничтожается. И JavaScript может назначать возвращаемое значение от multiply(2,2), которое является переменной 4 до twoByTow.

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

Закрытие

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

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

Как мы узнали ранее, после завершения выполнения функции counter вся локальная память удаляется, кроме возвращаемого значения: increment. Но для работы функции increment нужно count. Так где это возьмет? Вот хитрость!

Как только мы объявляем increment под капотом, движок JavaScript сразу же добавляет к нему скрытое свойство [[scope]].

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

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

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

Я настоятельно рекомендую посмотреть это видео: Закрытие, объем и контекст выполнения по Уиллу Сентансу, чтобы полностью понять эту тему.

Подробнее:

этот

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

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

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

this — это не фиксированная характеристика функции, основанная на определении функции, а скорее динамическая характеристика, которая определяется каждый раз при вызове функции.

В большинстве случаев значение this определяется тем, как вызывается функция (привязка во время выполнения). Его нельзя установить путем присваивания во время выполнения, и он может быть другим при каждом вызове функции. В ES5 появился метод bind() для установки значения функции this независимо от того, как она вызывается, а в ES2015 появились стрелочные функции, которые не предоставляют собственную привязку this (оно сохраняет значение this окружающего лексического контекста).

Подробнее:

Стрелочные функции

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

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

  • Стрелочные функции не имеют собственных привязок к this, arguments или super и не должны использоваться как методы.
  • Стрелочные функции не имеют доступа к ключевому слову new.target.
  • Стрелочные функции не подходят для методов call, apply и bind, которые обычно основаны на установлении области действия.
  • Стрелочные функции нельзя использовать в качестве конструкторов.
  • Стрелочные функции не могут использовать yield в своем теле.

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

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

Использование модификаторов функций .apply(), .call() или .bind() НЕ изменит область действия свойства this стрелочной функции. Если вы находитесь в ситуации, когда вам нужно привязать this к другой области, вы должны использовать обычную функцию JavaScript.

Подробнее: