- Устаревшее ключевое слово «var»
- Для «var» не существует блочной области видимости
- «var» допускает повторное объявление
- «var» обрабатываются в начале запуска функции
- Итого
- Функциональные выражения
- Объявление Function Expression
- Сравнение с Function Declaration
- Условное объявление функции
- Анонимные функции
- new Function
- Итого
Устаревшее ключевое слово «var»
В самой первой главе про переменные мы ознакомились с тремя способами объявления переменных:
let и const ведут себя одинаково по отношению к лексическому окружению, области видимости.
Но var – это совершенно другой зверь, берущий своё начало с давних времён. Обычно var не используется в современных скриптах, но всё ещё может скрываться в старых.
Если в данный момент вы не работаете с подобными скриптами, вы можете пропустить или отложить прочтение данной главы, однако, есть шанс, что вы столкнётесь с var в будущем.
На первый взгляд, поведение var похоже на let . Например, объявление переменной:
function sayHi() < var phrase = "Привет"; // локальная переменная, "var" вместо "let" alert(phrase); // Привет >sayHi(); alert(phrase); // Ошибка: phrase не определена
…Однако, отличия всё же есть.
Для «var» не существует блочной области видимости
Область видимости переменных var ограничивается либо функцией, либо, если переменная глобальная, то скриптом. Такие переменные доступны за пределами блока.
if (true) < var test = true; // используем var вместо let >alert(test); // true, переменная существует вне блока if
Так как var игнорирует блоки, мы получили глобальную переменную test .
А если бы мы использовали let test вместо var test , тогда переменная была бы видна только внутри if :
if (true) < let test = true; // используем let >alert(test); // Error: test is not defined
Аналогично для циклов: var не может быть блочной или локальной внутри цикла:
for (var i = 0; i < 10; i++) < // . >alert(i); // 10, переменная i доступна вне цикла, т.к. является глобальной переменной
Если блок кода находится внутри функции, то var становится локальной переменной в этой функции:
function sayHi() < if (true) < var phrase = "Привет"; >alert(phrase); // срабатывает и выводит "Привет" > sayHi(); alert(phrase); // Ошибка: phrase не определена (видна в консоли разработчика)
Как мы видим, var выходит за пределы блоков if , for и подобных. Это происходит потому, что на заре развития JavaScript блоки кода не имели лексического окружения. Поэтому можно сказать, что var – это пережиток прошлого.
«var» допускает повторное объявление
Если в блоке кода дважды объявить одну и ту же переменную let , будет ошибка:
let user; let user; // SyntaxError: 'user' has already been declared
Используя var , можно переобъявлять переменную сколько угодно раз. Повторные var игнорируются:
var user = "Pete"; var user; // ничего не делает, переменная объявлена раньше // . нет ошибки alert(user); // Pete
Если дополнительно присвоить значение, то переменная примет новое значение:
var user = "Pete"; var user = "John"; alert(user); // John
«var» обрабатываются в начале запуска функции
Объявления переменных var обрабатываются в начале выполнения функции (или запуска скрипта, если переменная является глобальной).
Другими словами, переменные var считаются объявленными с самого начала исполнения функции вне зависимости от того, в каком месте функции реально находятся их объявления (при условии, что они не находятся во вложенной функции).
…Технически полностью эквивалентен следующему (объявление переменной var phrase перемещено в начало функции):
…И даже коду ниже (как вы помните, блочная область видимости игнорируется):
function sayHi() < phrase = "Привет"; // (*) if (false) < var phrase; >alert(phrase); > sayHi();
Это поведение называется «hoisting» (всплытие, поднятие), потому что все объявления переменных var «всплывают» в самый верх функции.
В примере выше if (false) условие никогда не выполнится. Но это никаким образом не препятствует созданию переменной var phrase , которая находится внутри него, поскольку объявления var «всплывают» в начало функции. Т.е. в момент присвоения значения (*) переменная уже существует.
Объявления переменных «всплывают», но присваивания значений – нет.
Это проще всего продемонстрировать на примере:
Строка var phrase = «Привет» состоит из двух действий:
Объявление переменной обрабатывается в начале выполнения функции («всплывает»), однако присвоение значения всегда происходит в той строке кода, где оно указано. Т.е. код выполняется по следующему сценарию:
Поскольку все объявления переменных var обрабатываются в начале функции, мы можем ссылаться на них в любом месте. Однако, переменные имеют значение undefined до строки с присвоением значения.
В обоих примерах выше вызов alert происходил без ошибки, потому что переменная phrase уже существовала. Но её значение ещё не было присвоено, поэтому мы получали undefined .
Итого
Существует 2 основных отличия var от let/const :
- Переменные var не имеют блочной области видимости, они ограничены, как минимум, телом функции.
- Объявления (инициализация) переменных var производится в начале исполнения функции (или скрипта для глобальных переменных).
Есть ещё одно небольшое отличие, относящееся к глобальному объекту, мы рассмотрим его в следующей главе.
Эти особенности, как правило, не очень хорошо влияют на код. Блочная область видимости – это удобно. Поэтому много лет назад let и const были введены в стандарт и сейчас являются основным способом объявления переменных.
Функциональные выражения
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/function-expressions.
В JavaScript функция является значением, таким же как строка или число.
Как и любое значение, объявленную функцию можно вывести, вот так:
function sayHi() < alert( "Привет" ); >alert( sayHi ); // выведет код функции
Обратим внимание на то, что в последней строке после sayHi нет скобок. То есть, функция не вызывается, а просто выводится на экран.
Функцию можно скопировать в другую переменную:
function sayHi() < // (1) alert( "Привет" ); >var func = sayHi; // (2) func(); // Привет // (3) sayHi = null; sayHi(); // ошибка (4)
- Объявление (1) как бы говорит интерпретатору «создай функцию и помести её в переменную sayHi
- В строке (2) мы копируем функцию в новую переменную func . Ещё раз обратите внимание: после sayHi нет скобок. Если бы они были, то вызов var func = sayHi() записал бы в func результат работы sayHi() (кстати, чему он равен? правильно, undefined , ведь внутри sayHi нет return ).
- На момент (3) функцию можно вызывать и как sayHi() и как func()
- …Однако, в любой момент значение переменной можно поменять. При этом, если оно не функция, то вызов (4) выдаст ошибку.
Обычные значения, такие как числа или строки, представляют собой данные. А функцию можно воспринимать как действие.
Это действие можно запустить через скобки () , а можно и скопировать в другую переменную, как было продемонстрировано выше.
Объявление Function Expression
Существует альтернативный синтаксис для объявления функции, который ещё более наглядно показывает, что функция – это всего лишь разновидность значения переменной.
Он называется «Function Expression» (функциональное выражение) и выглядит так:
var sayHi = function(person) < alert( "Привет, " + person ); >; sayHi('Вася');
Сравнение с Function Declaration
«Классическое» объявление функции, о котором мы говорили до этого, вида function имя(параметры) <. >, называется в спецификации языка «Function Declaration».
- Function Declaration – функция, объявленная в основном потоке кода.
- Function Expression – объявление функции в контексте какого-либо выражения, например присваивания.
Несмотря на немного разный вид, по сути две эти записи делают одно и то же:
// Function Declaration function sum(a, b) < return a + b; >// Function Expression var sum = function(a, b)
Оба этих объявления говорят интерпретатору: «объяви переменную sum , создай функцию с указанными параметрами и кодом и сохрани её в sum «.
Основное отличие между ними: функции, объявленные как Function Declaration, создаются интерпретатором до выполнения кода.
Поэтому их можно вызвать до объявления, например:
sayHi("Вася"); // Привет, Вася function sayHi(name)
А если бы это было объявление Function Expression, то такой вызов бы не сработал:
sayHi("Вася"); // ошибка! var sayHi = function(name)
Это из-за того, что JavaScript перед запуском кода ищет в нём Function Declaration (их легко найти: они не являются частью выражений и начинаются со слова function ) и обрабатывает их.
А Function Expression создаются в процессе выполнения выражения, в котором созданы, в данном случае – функция будет создана при операции присваивания sayHi = function.
Как правило, возможность Function Declaration вызвать функцию до объявления – это удобно, так как даёт больше свободы в том, как организовать свой код.
Можно расположить функции внизу, а их вызов – сверху или наоборот.
Условное объявление функции
В некоторых случаях «дополнительное удобство» Function Declaration может сослужить плохую службу.
Например, попробуем, в зависимости от условия, объявить функцию sayHi по-разному:
var age = +prompt("Сколько вам лет?", 20); if (age >= 18) < function sayHi() < alert( 'Прошу вас!' ); >> else < function sayHi() < alert( 'До 18 нельзя' ); >> sayHi();
Function Declaration при use strict видны только внутри блока, в котором объявлены. Так как код в учебнике выполняется в режиме use strict , то будет ошибка.
А что, если использовать Function Expression?
var age = prompt('Сколько вам лет?'); var sayHi; if (age >= 18) < sayHi = function() < alert( 'Прошу Вас!' ); >> else < sayHi = function() < alert( 'До 18 нельзя' ); >> sayHi();
var age = prompt('Сколько вам лет?'); var sayHi = (age >= 18) ? function() < alert('Прошу Вас!'); >: function() < alert('До 18 нельзя'); >; sayHi();
Оба этих варианта работают правильно, поскольку, в зависимости от условия, создаётся именно та функция, которая нужна.
Анонимные функции
Взглянем ещё на один пример – функцию ask(question, yes, no) с тремя параметрами:
question Строка-вопрос yes Функция no Функция
Она выводит вопрос на подтверждение question и, в зависимости от согласия пользователя, вызывает функцию yes() или no() :
function ask(question, yes, no) < if (confirm(question)) yes() else no(); >function showOk() < alert( "Вы согласились." ); >function showCancel() < alert( "Вы отменили выполнение." ); >// использование ask("Вы согласны?", showOk, showCancel);
Какой-то очень простой код, не правда ли? Зачем, вообще, может понадобиться такая ask ?
…Оказывается, при работе со страницей такие функции как раз очень востребованы, только вот спрашивают они не простым confirm , а выводят более красивое окно с вопросом и могут интеллектуально обработать ввод посетителя. Но это всё потом, когда перейдём к работе с интерфейсом.
Здесь же обратим внимание на то, что то же самое можно написать более коротко:
function ask(question, yes, no) < if (confirm(question)) yes() else no(); >ask( "Вы согласны?", function() < alert("Вы согласились."); >, function() < alert("Вы отменили выполнение."); >);
Здесь функции объявлены прямо внутри вызова ask(. ) , даже без присвоения им имени.
Функциональное выражение, которое не записывается в переменную, называют анонимной функцией.
Действительно, зачем нам записывать функцию в переменную, если мы не собираемся вызывать её ещё раз? Можно просто объявить непосредственно там, где функция нужна.
Такого рода код возникает естественно, он соответствует «духу» JavaScript.
new Function
Существует ещё один способ создания функции, который используется очень редко, но упомянем и его для полноты картины.
Он позволяет создавать функцию полностью «на лету» из строки, вот так:
var sum = new Function('a,b', ' return a+b; '); var result = sum(1, 2); alert( result ); // 3
То есть, функция создаётся вызовом new Function(params, code) :
params Параметры функции через запятую в виде строки. code Код функции в виде строки.
Таким образом можно конструировать функцию, код которой неизвестен на момент написания программы, но строка с ним генерируется или подгружается динамически во время её выполнения.
Пример использования – динамическая компиляция шаблонов на JavaScript, мы встретимся с ней позже, при работе с интерфейсами.
Итого
Функции в JavaScript являются значениями. Их можно присваивать, передавать, создавать в любом месте кода.
- Если функция объявлена в основном потоке кода, то это Function Declaration.
- Если функция создана как часть выражения, то это Function Expression.
Между этими двумя основными способами создания функций есть следующие различия:
Function Declaration | Function Expression | |
---|---|---|
Время создания | До выполнения первой строчки кода. | Когда управление достигает строки с функцией. |
Можно вызвать до объявления | Да (т.к. создаётся заранее) | Нет |
Условное объявление в if | Не работает | Работает |
Иногда в коде начинающих разработчиков можно увидеть много Function Expression. Почему-то, видимо, не очень понимая происходящее, функции решают создавать как var func = function() , но в большинстве случаев обычное объявление функции – лучше.
Если нет явной причины использовать Function Expression – предпочитайте Function Declaration.
// Function Expression var f = function() < . >// Function Declaration function f()
Function Declaration короче и лучше читается. Дополнительный бонус – такие функции можно вызывать до того, как они объявлены.
Используйте Function Expression только там, где это действительно нужно и удобно.