Эта статья предназначена для понимания старых скриптов.
Информация в этой статье полезна для понимания старых скриптов.
Мы не так пишем новый код.
В самой первой главе о переменных мы упомянули три способа объявления переменных:
let
const
var
Объявление var
похоже на let
. В большинстве случаев мы можем заменить let
на var
или наоборот и ожидать, что всё заработает:
вар сообщение = "Привет"; предупреждение (сообщение); // Привет
Но внутренне var
— совсем другой зверь, берущий свое начало из очень давних времен. Обычно он не используется в современных сценариях, но все еще присутствует в старых.
Если вы не планируете знакомиться с такими сценариями, вы можете даже пропустить эту главу или отложить ее.
С другой стороны, важно понимать различия при переносе старых скриптов с var
на let
, чтобы избежать нечетных ошибок.
Переменные, объявленные с помощью var
, имеют либо область действия функции, либо глобальную область видимости. Их видно через блоки.
Например:
если (истина) { вар тест = правда; // используем «var» вместо «let» } оповещение (тест); // правда, переменная живет после if
Поскольку var
игнорирует блоки кода, у нас есть test
глобальной переменной.
Если бы мы использовали let test
вместо var test
, то переменная была бы видна внутри только в if
:
если (истина) { пусть тест = правда; // используем "позволить" } оповещение (тест); // Ошибка ссылки: тест не определен
То же самое и с циклами: var
не может быть блочным или локальным в цикле:
для (вар я = 0; я <10; я++) { вар один = 1; // ... } предупреждение (я); // 10, после цикла видно "i", это глобальная переменная предупреждение (один); // 1, после цикла видно "единицу", это глобальная переменная
Если блок кода находится внутри функции, то var
становится переменной уровня функции:
функция SayHi() { если (истина) { var фраза = "Привет"; } предупреждение (фраза); // работает } сказатьПривет(); предупреждение (фраза); // Ошибка ссылки: фраза не определена
Как мы видим, var
проникает через if
, for
или другие блоки кода. Это потому, что давным-давно в JavaScript блоки не имели лексического окружения, и var
является остатком этого.
Если мы объявим одну и ту же переменную с помощью let
дважды в одной области видимости, это ошибка:
пусть пользователь; пусть пользователь; // Синтаксическая ошибка: 'пользователь' уже объявлен
С помощью var
мы можем переобъявлять переменную любое количество раз. Если мы используем var
с уже объявленной переменной, она просто игнорируется:
вар пользователь = "Пит"; вар пользователь = "Джон"; // эта "var" ничего не делает (уже объявлена) // ...это не вызывает ошибку оповещение (пользователь); // Джон
Объявления var
обрабатываются при запуске функции (или запуске сценария для глобальных переменных).
Другими словами, переменные var
определяются с начала функции, независимо от того, где находится определение (при условии, что определение не находится во вложенной функции).
Итак, этот код:
функция SayHi() { фраза = "Привет"; предупреждение (фраза); вар фраза; } сказатьПривет();
…Технически это то же самое (перемещенная var phrase
выше):
функция SayHi() { вар фраза; фраза = "Привет"; предупреждение (фраза); } сказатьПривет();
…Или даже так (помните, блоки кода игнорируются):
функция SayHi() { фраза = "Привет"; // (*) если (ложь) { вар фраза; } предупреждение (фраза); } сказатьПривет();
Люди также называют такое поведение «поднятием» (поднятием), потому что все var
«поднимаются» (поднимаются) на вершину функции.
Итак, в приведенном выше примере ветка if (false)
никогда не выполняется, но это не имеет значения. var
внутри нее обрабатывается в начале функции, поэтому в момент (*)
переменная существует.
Декларации принимаются, а задания – нет.
Лучше всего это продемонстрировать на примере:
функция SayHi() { предупреждение (фраза); var фраза = "Привет"; } сказатьПривет();
Строка var phrase = "Hello"
имеет в себе два действия:
Объявление переменной var
Назначение переменной =
.
Объявление обрабатывается в начале выполнения функции («поднято»), но присвоение всегда срабатывает в том месте, где оно появилось. Таким образом, код работает примерно так:
функция SayHi() { вар фраза; // объявление работает в начале... предупреждение (фраза); // неопределенный фраза = "Привет"; // ...назначение - когда выполнение достигает его. } сказатьПривет();
Поскольку все объявления var
обрабатываются при запуске функции, мы можем ссылаться на них в любом месте. Но переменные не определены до выполнения присваиваний.
В обоих приведенных выше примерах alert
выполняется без ошибок, поскольку существует переменная phrase
. Но его значение еще не присвоено, поэтому оно отображается undefined
.
Раньше, поскольку существовал только var
и он не был виден на уровне блоков, программисты изобрели способ его эмуляции. То, что они делали, называлось «выражениями функций с немедленным вызовом» (сокращенно IIFE).
Это не то, что нам следует использовать в настоящее время, но вы можете найти их в старых скриптах.
IIFE выглядит следующим образом:
(функция() { вар сообщение = "Привет"; предупреждение (сообщение); // Привет })();
Здесь создается и немедленно вызывается функциональное выражение. Таким образом, код выполняется сразу и имеет свои собственные переменные.
Выражение функции заключено в круглые скобки (function {...})
, поскольку когда движок JavaScript встречает "function"
в основном коде, он воспринимает ее как начало объявления функции. Но объявление функции должно иметь имя, поэтому такой код выдаст ошибку:
// Пытается объявить и немедленно вызвать функцию function() { // <-- SyntaxError: Операторы функции требуют имени функции вар сообщение = "Привет"; предупреждение (сообщение); // Привет }();
Даже если мы скажем: «окей, давайте добавим имя», это не сработает, поскольку JavaScript не позволяет сразу вызывать объявления функций:
// синтаксическая ошибка из-за круглых скобок ниже функция идти() { }(); // <-- невозможно вызвать объявление функции немедленно
Таким образом, круглые скобки вокруг функции — это трюк, позволяющий показать JavaScript, что функция создается в контексте другого выражения и, следовательно, является функциональным выражением: ей не нужно имя, и ее можно вызвать немедленно.
Помимо круглых скобок, существуют и другие способы сообщить JavaScript, что мы имеем в виду функциональное выражение:
// Способы создания IIFE (функция() { alert("Функция в скобках"); })(); (функция() { alert("Круглые скобки вокруг всего этого"); }()); !функция() { alert("Побитовый оператор NOT запускает выражение"); }(); +функция() { alert("Унарный плюс начинает выражение"); }();
Во всех вышеперечисленных случаях мы объявляем функциональное выражение и немедленно запускаем его. Еще раз отметим: сейчас нет смысла писать такой код.
Есть два основных отличия var
от let/const
:
Переменные var
не имеют области действия блока, их видимость ограничена текущей функцией или глобальна, если они объявлены вне функции.
Объявления var
обрабатываются при запуске функции (запуск сценария для глобальных переменных).
Есть еще одно очень незначительное отличие, связанное с глобальным объектом, о котором мы поговорим в следующей главе.
Эти различия в большинстве случаев делают var
хуже, чем let
. Переменные уровня блока — это такая замечательная вещь. Вот почему let
был введен в стандарт уже давно и сейчас является основным способом (наряду с const
) объявления переменной.