Функции

Создание функций

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

function имя(параметры) {

}
var имя = function(параметры) {

}

var имя = new Function(параметры, ‘…’)
Именованные функции доступны везде в области видимости Анонимные — доступны только с момента объявления. Синтаксис используется редко, в основном для получения функции из текста, например, динамически загруженного с сервера в процессе выполнения скриптов.
/* функция sum 
определена ниже 
*/
var a = sum(2,2)

function sum(x,y) {
	return x+y
}
/* будет ошибка, 
т.к sum еще не существует
*/
var a = sum(2,2)

var sum = function(x,y) {
	return x+y
}

Частичное применение без контекста

Что если мы хотим зафиксировать некоторые аргументы, но не контекст ? Например, для метода объекта.

Встроенный не позволяет этого. Мы не можем просто опустить контекст и перейти к аргументам.

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

Вот так:

Результатом вызова будет обёртка , которая вызывает с:

  • Тем же , который она получает (для вызова – это будет )
  • Затем передаёт ей – аргументы из вызова ()
  • Затем передаёт ей – аргументы, полученные обёрткой ()

Благодаря оператору расширения реализовать это очень легко, не правда ли?

Также есть готовый вариант из библиотеки lodash.

Параметры по умолчанию

Можно указывать параметры по умолчанию через равенство , например:

Параметр по умолчанию используется при отсутствующем аргументе или равном , например:

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

Параметры по умолчанию могут быть не только значениями, но и выражениями.

Например:

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

В частности, выражение по умолчанию не вычисляется при объявлении функции. В примере выше функция будет вызвана именно в последней строке, так как не передан параметр.

Вложенные функции

Функция называется «вложенной», когда она создаётся внутри другой функции.

Это очень легко сделать в JavaScript.

Мы можем использовать это для упорядочивания нашего кода, например, как здесь:

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

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

Не важно где, она всё так же будет иметь доступ к тем же внешним переменным

Например, здесь, вложенная функция присваивается новому объекту в конструкторе:

А здесь мы просто создаём и возвращаем функцию «счётчик»:

Давайте продолжим с примером . Он создаёт функцию «counter», которая возвращает следующее число при каждом вызове. Несмотря на простоту, немного модифицированные варианты этого кода применяются на практике, например, в генераторе псевдослучайных чисел и во многих других случаях.

Как же это работает изнутри?

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

  1. Локальные переменные вложенной функции…
  2. Переменные внешней функции…
  3. И так далее, пока не будут достигнуты глобальные переменные.

В этом примере будет найден на шаге . Когда внешняя переменная модифицируется, она изменится там, где была найдена. Значит, найдёт внешнюю переменную и увеличит её значение в лексическом окружении, которому она принадлежит. Как если бы у нас было .

Теперь рассмотрим два вопроса:

  1. Можем ли мы каким-нибудь образом сбросить счётчик из кода, который не принадлежит ? Например, после вызова в коде выше.
  2. Если мы вызываем несколько раз – нам возвращается много функций . Они независимы или разделяют одну и ту же переменную ?

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

Готовы?

Хорошо, давайте ответим на вопросы.

  1. Такой возможности нет: – локальная переменная функции, мы не можем получить к ней доступ извне.
  2. Для каждого вызова создаётся новое лексическое окружение функции, со своим собственным . Так что, получившиеся функции – независимы.

Вот демо:

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

Неотложные функции

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

Есть два наиболее распространенных синтаксиса:

(function optional_function_name() {
  //body
}());
(function optional_function_name() {
  //body
})();

Могут всречаться и другие способы:

// Другие способы
!function() { /* ... */ }();
+function() { /* ... */ }();
new function() { /* ... */ };

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

var page_language = (function () {
  var lang;
  // Код для получения языка страницы
  return lang;
})();

Решение 2: привязать контекст с помощью bind

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

Базовый синтаксис :

Результатом вызова является особый «экзотический объект» (термин взят из спецификации), который вызывается как функция и прозрачно передаёт вызов в , при этом устанавливая .

Другими словами, вызов подобен вызову с фиксированным .

Например, здесь передаёт вызов в , фиксируя :

Здесь – это «связанный вариант» , с фиксированным .

Все аргументы передаются исходному методу как есть, например:

Теперь давайте попробуем с методом объекта:

В строке мы берём метод и привязываем его к . Теперь – это «связанная» функция, которая может быть вызвана отдельно или передана в (контекст всегда будет правильным).

Здесь мы можем увидеть, что исправляет только , а аргументы передаются как есть:

Удобный метод:

Если у объекта много методов и мы планируем их активно передавать, то можно привязать контекст для них всех в цикле:

Некоторые JS-библиотеки предоставляют встроенные функции для удобной массовой привязки контекста, например в lodash.

Возвращаемые значения

Функции могут не только принимать входящие данные из внешней программы, но и способны пере­давать обратно значения по завершении своих инструкций. Для возврата значения используется инструкция return.

Инструкция return имеет следующий синтаксис:

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

Выполнить код »
Скрыть результаты

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

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

Выполнить код »
Скрыть результаты

В инструкции можно указать сразу выражение, перепишем нашу
функцию :

Выполнить код »
Скрыть результаты

Внутри функции можно использовать несколько инструкций :

Выполнить код »
Скрыть результаты

В случае, если в функции нет инструкции или указана без аргументов, то функция вернёт значение :

Выполнить код »
Скрыть результаты

setTimeout и setInterval

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

Отличаются они друг от друга лишь тем, что выполняет вызов функции всего один раз, а – постоянно через указанный интервал времени.

Синтаксис и :

// setTimeout
window.setTimeout(func  );
// setInterval
window.setTimeout(func  );

Параметры:

  • – функция, которую нужно вызвать спустя указанное в количество миллисекунд;
  • – количество миллисекунд по истечении которых нужно выполнить ;
  • – дополнительные аргументы, которые нужно передать в .

Например, вызовем функцию один раз по прошествии 3 секунд:

// функция
function myFunc() {
  console.log('after 3 seconds');
}

// вызовем функцию myFunc после 3 секунд
window.setTimeout(myFunc, 3000);
// выведем сообщение в консоль
console.log('immediately');

При выполнение этого кода программа не остановится на месте, где мы зарегистрировали некоторую асинхронность посредством и не будет блокировать её дальнейшее выполнение на 3 секунды, через которые нужно будет вызвать . Выполнение скрипта продолжится дальше и сначала браузер выведет в консоль сообщение «immediately», а затем через 3 секунды – «after 3 seconds».

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

Чтобы более подробно разобраться в этом необходимо рассмотреть, как движок JavaScript выполняет синхронный и асинхронный код, а также что такое event loop и как он работает.

Синхронный и асинхронный код

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

Выполнение такого кода движок JavaScript выполняет последовательно (т.е. строчку за строчкой). При этом перед тем, как выполнить какую-то строчку кода интерпретатор сначала помещает её в стек вызовов (call stack). Именно в нём происходит её разбор и исполнение. После этого происходит её извлечение из стека и переход к следующей строчке.

Но всё меняется, когда интерпретатор доходит до выполнения асинхронных операций, например . Они также как и синхронные операции сначала попадают в стек вызовов, где происходит их разбор. Но, при разборе интерпретатор понимает, что это некоторый вызов Web API и помещает эту операцию в него. После этого он удаляет эту строчку из call stack и переходит к выполнению следующей строчки кода.

В это же время Web API регистрирует эту функцию и запускает таймер. Как только он завершается, он помещает эту функцию в очередь (callback queue). Очередь – это структура данных типа FIFO. Она хранит все функции в том порядке, в котором они были туда добавлены.

Очередь обратных вызовов (callback queue) обрабатывает цикл событий (event loop). Он смотрит на эту очередь и на стек вызовов (call stack). Если стек вызовов пуст, а очередь нет – то он берёт первую функцию из очереди и закидывает её в стек вызовов, в котором она уже выполняется. Вот таким образом происходит выполнения асинхронного кода в JavaScript.

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

window.setInterval(myFunc, 3000);

Пример, с передачей функции аргументов:

function sayHello(name) {
  alert(`Привет, ${name}!`);
}
setTimeout(sayHello, 3000, 'Василий'); // Привет, Василий!

Пример, с использованием в анонимной функции:

setTimeout(function (name) {
  alert(`Привет, ${name}!`);
}, 3000, 'Василий');

В и можно также вместо функции передавать строку с кодом, который нужно выполнить через определённый интервал времени. Но этот вариант использовать не рекомендуется по тем же причинам, что и функцию .

Если функция по каким-то причинам не работает, то проверьте действительно ли вы передаёте ссылку на функцию, а неё результат:

function sayHello() {
  console.log('Привет!');
}

// передаём в setTimeout не ссылку на функцию sayHello, а результат её вызова
setTimeout(sayHello(), 3000);

Объект arguments

В JavaScript можно создавать функции с произвольным числом аргументов. Доступ ко всем указанным при вызове функции аргументам, в том числе к лишним аргументам, которым не были назначены параметры, осуществляет­ся через объект arguments, который доступен только внутри тела функции.

Свойство этого объекта содержит число переданных аргументов.

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

Выполнить код »
Скрыть результаты

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

Поскольку он похож на массив, обратиться к переданным функции аргументам можно так же, как и к элементам массива: первый аргумент будет доступен в теле функ­ции как элемент массива arguments,

второй аргумент

arguments

Функция, приводимая в следующем примере, возвращает строку, содержащую значения параметров (переданные аргументы) и их общее количество, которые были указаны в вызове функции (не в ее определении):

Выполнить код »
Скрыть результаты

Лексическое окружение

Все переменные внутри функции – это свойства специального внутреннего объекта , который создаётся при её запуске.

Мы будем называть этот объект «лексическое окружение» или просто «объект переменных».

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

В отличие от , объект является внутренним, он скрыт от прямого доступа.

Посмотрим пример, чтобы лучше понимать, как это работает:

При вызове функции:

  1. До выполнения первой строчки её кода, на стадии инициализации, интерпретатор создаёт пустой объект и заполняет его.

    В данном случае туда попадает аргумент и единственная переменная :

  2. Функция выполняется.

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

  3. В конце выполнения функции объект с переменными обычно выбрасывается и память очищается. В примерах выше так и происходит. Через некоторое время мы рассмотрим более сложные ситуации, при которых объект с переменными сохраняется и после завершения функции.

Тонкости спецификации

Если почитать спецификацию ECMA-262, то мы увидим, что речь идёт о двух объектах: и .

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

Более формальное описание находится в спецификации ECMA-262, секции 10.2-10.5 и 13.

Function Expression в сравнении с Function Declaration

Давайте разберём ключевые отличия Function Declaration от Function Expression.

Во-первых, синтаксис: как определить, что есть что в коде.

  • Function Declaration: функция объявляется отдельной конструкцией «function…» в основном потоке кода.

  • Function Expression: функция, созданная внутри другого выражения или синтаксической конструкции. В данном случае функция создаётся в правой части «выражения присваивания» :

Более тонкое отличие состоит, в том, когда создаётся функция движком JavaScript.

Function Expression создаётся, когда выполнение доходит до него, и затем уже может использоваться.

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

С Function Declaration всё иначе.

Function Declaration можно использовать во всем скрипте (или блоке кода, если функция объявлена в блоке).

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

И только после того, как все объявления Function Declaration будут обработаны, продолжится выполнение.

В результате, функции, созданные, как Function Declaration могут быть вызваны раньше своих определений.

Например, так будет работать:

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

…Если бы это было Function Expression, то такой код вызовет ошибку:

Функции, объявленные при помощи Function Expression, создаются тогда, когда выполнение доходит до них. Это случится только на строке, помеченной звёздочкой . Слишком поздно.

Ещё одна важная особенность Function Declaration заключается в их блочной области видимости.

В строгом режиме, когда Function Declaration находится в блоке , функция доступна везде внутри блока. Но не снаружи него.

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

Такой код, использующий Function Declaration, работать не будет:

Это произошло, так как объявление Function Declaration видимо только внутри блока кода, в котором располагается.

Вот ещё один пример:

Что можно сделать, чтобы была видима снаружи ?

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

Такой код работает, как ожидалось:

Можно упростить этот код ещё сильнее, используя условный оператор :

Когда использовать Function Declaration, а когда Function Expression?

Как правило, если нам понадобилась функция, в первую очередь нужно рассматривать синтаксис Function Declaration, который мы использовали до этого. Он даёт нам больше свободы в том, как мы можем организовывать код. Функции, объявленные таким образом, можно вызывать до их объявления.

Также функции вида чуть более заметны в коде, чем . Function Declaration легче «ловятся глазами».

…Но если Function Declaration нам не подходит по какой-то причине (мы рассмотрели это в примере выше), то можно использовать объявление при помощи Function Expression.

Function Expressions

A JavaScript function can also be defined using an expression.

A function expression can be stored in a variable:

Example

var x = function (a, b) {return a * b};

After a function expression has been stored in a variable, the variable can
be used as a function:

Example

var x = function (a, b) {return a * b};
var z = x(4, 3);

The function above is actually an anonymous function (a function without a
name).

Functions stored in variables do not need function names. They are always
invoked (called) using the variable name.

The function above ends with a semicolon because it is a part of an executable statement.

Стрелочные функции стандарта «ES6»

Стандарт «ES6» привнес в js новый синтаксис объявления функций. За свой вид такой подход получил название «Стрелочные функции».

Стандарт «ES6» поддерживается только новыми браузерами.

// Было
var sing = function(){ console.log('singing...') };
// Стало
var sing = () => { console.log('singing...') };

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

// Было
setInterval(function () {
  console.log('message')
}, 1000);
// Стало
setInterval(() => console.log('message'), 1000);

Что такое JavaScript?

Изначально JavaScript был создан, чтобы «сделать веб-страницы живыми».

Программы на этом языке называются скриптами. Они могут встраиваться в HTML и выполняться автоматически при загрузке веб-страницы.

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

Это отличает JavaScript от другого языка – Java.

Почему JavaScript?

Когда JavaScript создавался, у него было другое имя – «LiveScript». Однако, язык Java был очень популярен в то время, и было решено, что позиционирование JavaScript как «младшего брата» Java будет полезно.

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

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

У браузера есть собственный движок, который иногда называют «виртуальная машина JavaScript».

Разные движки имеют разные «кодовые имена». Например:

  • V8 – в Chrome и Opera.
  • SpiderMonkey – в Firefox.
  • …Ещё есть «Trident» и «Chakra» для разных версий IE, «ChakraCore» для Microsoft Edge, «Nitro» и «SquirrelFish» для Safari и т.д.

Эти названия полезно знать, так как они часто используются в статьях для разработчиков. Мы тоже будем их использовать. Например, если «функциональность X поддерживается V8», тогда «Х», скорее всего, работает в Chrome и Opera.

Как работают движки?

Движки сложны. Но основы понять легко.

  1. Движок (встроенный, если это браузер) читает («парсит») текст скрипта.
  2. Затем он преобразует («компилирует») скрипт в машинный язык.
  3. После этого машинный код запускается и работает достаточно быстро.

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

Итого

Чтобы сгенерировать событие из кода, вначале надо создать объект события.

Базовый конструктор принимает обязательное имя события и – объект с двумя свойствами:

  • чтобы событие всплывало.
  • если мы хотим, чтобы работал.

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

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

Несмотря на техническую возможность генерировать встроенные браузерные события типа или , пользоваться ей стоит с большой осторожностью. Весьма часто, когда разработчик хочет сгенерировать встроенное событие – это вызвано «кривой» архитектурой кода

Весьма часто, когда разработчик хочет сгенерировать встроенное событие – это вызвано «кривой» архитектурой кода.

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

  • Либо как явный и грубый хак, чтобы заставить работать сторонние библиотеки, в которых не предусмотрены другие средства взаимодействия.
  • Либо для автоматического тестирования, чтобы скриптом «нажать на кнопку» и посмотреть, произошло ли нужное действие.

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

Итого

Функции в JavaScript являются значениями. Их можно присваивать, передавать, создавать в любом месте кода.

  • Если функция объявлена в основном потоке кода, то это Function Declaration.
  • Если функция создана как часть выражения, то это Function Expression.

Между этими двумя основными способами создания функций есть следующие различия:

Function Declaration Function Expression
Время создания До выполнения первой строчки кода. Когда управление достигает строки с функцией.
Можно вызвать до объявления (т.к. создаётся заранее)
Условное объявление в

Иногда в коде начинающих разработчиков можно увидеть много Function Expression. Почему-то, видимо, не очень понимая происходящее, функции решают создавать как , но в большинстве случаев обычное объявление функции – лучше.

Если нет явной причины использовать Function Expression – предпочитайте Function Declaration.

Сравните по читаемости:

Function Declaration короче и лучше читается. Дополнительный бонус – такие функции можно вызывать до того, как они объявлены.

Используйте Function Expression только там, где это действительно нужно и удобно.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector