Variable Declarations #
let и const — относительно новые типы объявления переменных в JavaScript. Как мы упомянули ранее, let похож на var в некотором смысле, но позволяет пользователям избежать некоторые из общих ошибок, с которыми сталкиваются в JavaScript. const это расширение let , которое предотвращает переопределение переменных.
Так как TypeScript является надстройкой над Javascript, язык также поддерживает let и const . Далее мы подробнее расскажем об этих новых объявлениях переменных и объясним, почему они более предпочтительны, чем var .
Если вы использовали JavaScript поверхностно, следующая секция секция поможет освежить некоторые важные моменты. Если вы хорошо знакомы со всеми причудами объявления var в JavaScript, вы можете пропустить эту часть.
Объявления var #
Объявление переменной в JavaScript всегда происходит с помощью ключевого слова var .
var a = 10;
Как вы наверняка поняли, мы только что объявили переменную с именем a и значением 10 .
Мы также можем объявить переменную внутри функции:
function f()
и мы также имеем доступ к этим переменным внутри других функций:
function f() < var a = 10; return function g() < var b = a + 1; return b; >> var g = f(); g(); // возвращает 11;
В примере выше g захватывает(замыкает в себе) переменную a , объявленную в f . В любой точке, где будет вызвана g , значение a будет связано со значением a в функции f . Даже если g вызвана однажды и f закончила выполнение, можно получить доступ и модифицировать a .
function f() < var a = 1; a = 2; var b = g(); a = 3; return b; function g() < return a; >> f(); // возвращает 2
Правила области видимости (Scoping)
Объявление var имеет несколько странных правил области видимости для тех, кто использует другие языки программирования. Посмотрите не следующий пример:
function f(shouldInitialize: boolean) < if (shouldInitialize) < var x = 10; >return x; > f(true); // returns '10' f(false); // returns 'undefined'
Некоторые могут повторно посмотреть на тот пример. Переменная x была объявлена внутри блока if , и мы можем получить к ней доступ вне этого блока. Это потому что объявления var доступны где бы то ни было внутри содержащей их функции, модуля, пространства имен(namespace) или же глобальной области видимости несмотря на блок, в котором они содержатся. Некоторые называют это var -видимость or function-видимость. Параметры также видны внутри функции.
Эти правила области видимости могут вызвать несколько типов ошибок. Одна из раздражающих проблем — это то, что не является ошибкой объявление переменной несколько раз:
function sumMatrix(matrix: number[][]) < var sum = 0; for (var i = 0; i < matrix.length; i++) < var currentRow = matrix[i]; for (var i = 0; i < currentRow.length; i++) < sum += currentRow[i]; >> return sum; >
Скорее всего несложно заметить, что внутренний цикл for случайно перезапишет переменную i , потому что i имеет области видимости внутри функции sumMatrix. Опытные разработчики знают, что похожие ошибки проскальзывают при code review и могут быть причиной бесконечной фрустрации.
Variable capturing quirks
Попробуйте быстро догадаться, какой будет вывод у этого кода:
for (var i = 0; i < 10; i++) < setTimeout(function() , 100 * i); >
Для тех, кто незнаком, setTimeout пытается выполнить функцию после указанного количества миллисекунд (при это ожидая, пока какой-либо другой код прекратит выполняться)
Готовы? Вот результат::
10 10 10 10 10 10 10 10 10 10
Многие JavaScript разработчики знакомы с таким поведением, но если вы удивлены, вы определенно не одиноки. Большинство ожидает, что вывод будет таким:
0 1 2 3 4 5 6 7 8 9
Помните, что мы упомянули ранее о замыкании переменных?
В любой точке, где будет вызвана g , значение a будет связано со значением a в функции f .
Давайте рассмотрим это в контексте нашего примера. setTimeout запустит функцию через несколько миллисекунд, после завершения цикла for . К моменту, когда цикл for закончит выполнение, i будет равняться 10 . Поэтому каждый раз, когда отложенная функция будет вызвана, она возвратит 10 !
Самый простой способ решить проблему — использовать немедленный запуск анонимной функции, чтобы захватить i на каждой итерации:
for (var i = 0; i < 10; i++) < // capture the current state of 'i' // by invoking a function with its current value (function(i) < setTimeout(function() < console.log(i); >, 100 * i); >)(i); >
Этот странно выглядящий шаблон на самом деле не редок.
Объявления let #
Сейчас мы уже понимаем, что var имеет некоторые проблемы, именно поэтому появился новый способ объявления переменных let . Они записываются точно также, как и объявления var .
let hello = "Hello!";
Ключевое различие не в синтаксисе, а в семантике, в которую мы сейчас погрузимся.
Блочная область видимости
Когда переменная объявляется с использованием let , она используется в режиме блочной области видимости. В отличие от переменных, объявленных с помощью var , чьи области видимости распространяются на всю функцию, в которой они находятся, переменные блочной области видимости не видимы вне их ближайшего блока или же цикла for .
function f(input: boolean) < let a = 100; if (input) < // Здесь мы видим переменную 'a' let b = a + 1; return b; >// Ошибка: 'b' не существует в этом блоке return b; >
Здесь мы имеем две локальные переменные a и b . Область видимости a ограничена телом функции f , в то время как область b ограничена блоком условия if .
Переменные, объявленные в блоке catch имеют те же правила видимости.
try < throw "oh no!"; >catch (e) < console.log("Oh well."); >// Error: 'e' doesn't exist here console.log(e);
Другое свойство переменных блочной области видимости — к ним нельзя обратиться перед тем, как они были объявлены. При том, что переменные блочной области видимости представлены везде в своем блоке, в каждой точке до их объявления находится мертвая зона. Это просто такой способ сказать, что вы не можете получить к ним доступ до утверждения let и, к счастью, TypeScript напомнит вам об этом.
a++; // неверно использовать 'a' до ее объявления; let a;
Однако, вы все еще можете захватить (замкнуть) переменную с блочной областью видимости до ее объявления. Правда, попытка вызвать такую функцию до ее объявления приведет к ошибке. Если вы компилируете в стандарт ES2015, это вызовет ошибку; тем не менее, прямо сейчас TypeScript разрешает это и не будет указывать на ошибку.
function foo() < // okay to capture 'a' return a; >// illegal call 'foo' before 'a' is declared // runtimes should throw an error here foo(); let a;
Для более подробной информации о мертвых зонах перейдите по этой ссылке: Mozilla Developer Network.
Повторное объявление и экранирование
В случае объявлений var не имеет значения, как много раз вы объявляете одну и ту же переменную. Вы всегда получите одну.
function f(x) < var x; var x; if (true) < var x; >>
В примере выше все объявления x на самом деле указывают на одну и ту же x , и это вполне допустимо. Это часто является источником багов. Поэтому хорошо, что объявления let этого не позволяют.
let x = 10; let x = 20; // Ошибка: нельзя переопределить 'x' в одной области видимости
Переменные не обязательно должны обе быть с блочной областью видимости в TypeScript, чтобы компилятором была указана ошибка.
function f(x) < let x = 100; // ошибка: пересекается с параметром функции >function g() < let x = 100; var x = 100; // ошибка: нельзя два раза объявить 'x' >
Это не значит, что переменная с блочной областью видимости не может быть объявлена с переменной с областью видимости в той же функции. Переменная с блочной областью просто должна быть объявлена в своем блоке
function f(condition, x) < if (condition) < let x = 100; return x; >return x; > f(false, 0); // returns 0 f(true, 0); // returns 100
Способ введения нового имени во вложенной области называется сокрытием. Это своего рода меч с двумя лезвиями, т.к. он может ввести некоторые баги, также как и избавить от других. Например, представьте, как мы могли бы переписать функцию sumMatrix , используя переменные let .
function sumMatrix(matrix: number[][]) < let sum = 0; for (let i = 0; i < matrix.length; i++) < var currentRow = matrix[i]; for (let i = 0; i < currentRow.length; i++) < sum += currentRow[i]; >> return sum; >
Эта версия цикла делает суммирование корректно, потому что i внутреннего цикла перекрывает i внешнего.
Такое сокрытие нужно обычно избегать, чтобы код был чище. Но в некоторых сценариях такой способ может идеально подходить для решения задачи. Вы должны использовать лучшее решение на ваше усмотрение.
Замыкание пременных с блочной областью видмости
Когда мы впервые коснулись замыкания переменных с объявлением var , мы коротко рассмотрели, как переменные ведут себя при замыкании. Чтобы лучше понимать суть, представьте себе, что каждый раз, когда появляется новая область видимости, она создает свою «среду» для переменных. Эта среда и ее захваченные извне переменные могут существовать даже после того, как все выражения внутри области видимости завершили свое выполнение.
function theCityThatAlwaysSleeps() < let getCity; if (true) < let city = "Seattle"; getCity = function() < return city; >> return getCity(); >
Из-за того, что мы захватили переменную city из ее среды, мы все еще можем получить к ней доступ, несмотря на тот факт, что блок if закончил выполнение. Вспомните наш предыдущий пример с setTimeout . Мы закончили на необходимости использовать IIFE, чтобы захватить состояние переменной для кждой итерации цикла for . В результате мы каждый раз создавали новую среду переменных для наших захваченных. Это доставляло немного боли, но, к счастью, нам не потребуется делать это снова в TypeScript.
Объявления let ведут себя совсем иначе, когда являются частью цикла. Вместо того, чтобы вводить новую среду для цикла, они вводят новую область видимости для каждой итерации. Так как это то, что мы делали с нашим IIFE, мы можем изменить наш старый пример setTimeout , используя объявления let .
for (let i = 0; i < 10 ; i++) < setTimeout(function() , 100 * i); >
and as expected, this will print out
0 1 2 3 4 5 6 7 8 9
Объявления const #
Объявления const — это еще один способ объявления переменных.
const numLivesForCat = 9;
Они такие же как и let , только, согласно их названию, их значение не может быть изменено после того, как им однажды уже присвоили значение. Другими словами, к ним применимы все правила области видимости let , но вы не можете их переназначить. Значение, с которым они связаны, является неизменным.
const numLivesForCat = 9; const kitty = < name: "Aurora", numLives: numLivesForCat, >// Ошибка kitty = < name: "Danielle", numLives: numLivesForCat >; // Все хорошо kitty.name = "Rory"; kitty.name = "Kitty"; kitty.name = "Cat"; kitty.numLives--;
Несмотря на то, что переменная была объявлена как const , ее внутренее состояние все еще может быть изменено. К счастью, TypeScript позволяет вам определить свойства объекта доступными только на чтение: readonly . Раздел в Интерфейсах объясняет детали этого.
let или const ? #
У нас есть два способа объявления с похожими правилами их области видимости, поэтому сам собой напрашивается вопрос о том, какой использовать. Ответ будет таким же, как и на большинство широких вопросов: это зависит от обстоятельств.
Применяя принцип наименьшего уровня привелегий, все объявления переменных, которые вы в дальнейшем не планируете менять, должны использовать const . Объясняется это тем, что если переменная не должна изменять свое значение, другие разработчики, которые работают над тем же кодом, не должны иметь возможность записи в объект. Это должно быть позволено только в случае реальной необходимости переназначения переменной. Использование const делает код более предсказуемым и понятным при объяснении потока данных.
Большая часть этого руководства использует объявления let .
Деструктурирование #
Еще одно нововведение из стандарта ECMAScript 2015, которое есть в TypeScript, это деструктурирование (прим. переводчика «destructuring» — не «уничтожение»). За полной информацией об этом пройдите по ссылке статья на Mozilla Developer Network. В этом разделе мы приведем сокращенный вариант.
Деструктурирование массивов
Самая простая форма деструктурирования — с использованием массива:
let input = [1, 2]; let [first, second] = input; console.log(first); // outputs 1 console.log(second); // outputs 2
Это создает две новых переменных с именами first и second . В сущности это эквивалент обращения по индексу, просто более удобный:
first = input[0]; second = input[1];
Деструктурирование также работает с ранее объявленными переменными:
// swap variables [first, second] = [second, first];
И с параметрами функции:
function f([first, second]: [number, number]) < console.log(first); console.log(second); >f(input);
Вы можете создать переменную для оставшихся элементов списка, используя ситаксис . name :
let [first, . rest] = [1, 2, 3, 4]; console.log(first); // outputs 1 console.log(rest); // outputs [ 2, 3, 4 ]
Конечно, т.к. это Javascript, вы можете просто проигнорировать их:
let [first] = [1, 2, 3, 4]; console.log(first); // outputs 1
Или какие-то определенные элементы:
let [, second, , fourth] = [1, 2, 3, 4];
Деструктурирование объекта
Вы можете также деструктурировать объекты:
let o = < a: "foo", b: 12, c: "bar" >let = o;
Этот код создает новые переменные a и b из o.a и o.b . Заметьте, что вы можете пропустить c , если она вам не нужна.
Как и в случае деструктурирования массива, вы можете назначить значения без объявления:
Заметьте, что мы должны окружить это выражение с помощью круглых скобок. JavaScript при парсинге рассматривает < как старт нового блока.
Переименование свойств
Вы можете также дать разные имена свойствам:
let = o;
Этот синтакс может немного сущать. Вы читаете a: newName1 как » a as newName1 «. Можно записать это по-другому, чтобы было понятнее:
let newName1 = o.a; let newName2 = o.b;
Сбивает с толку то, что двоеточие здесь не обозначает тип. Тип, если вы задаете его, все еще должен быть записан после деструктурирования:
let : = o;
Значения по умолчанию
Значения по умолчанию позволяют определить свойство, даже если оно не задавалось:
function keepWholeObject(wholeObject: ) < let = wholeObject; >
Функция keepWholeObject имеет переменную для wholeObject , как и свойства a and b , даже если b не определено.
Объявление функций
Деструктурирование также работает с объявлением функций Ниже приведен простой пример:
type C = function f(: C): void < // . >
Указание значений по умолчанию чаще употребляется для параметров, и использвание деструктурирования для этого может выглядеть запутанно. Прежде всего, вы должны помнить о необходимости указать тип до значения по умолчанию.
function f( = ): void < // . >f(); // ok, по умолчанию
Затем, вы должны не забыть дать значение по умолчанию для опциональных свойств деструктурированного параметра при определении функции. Помните также, что C был определен с опциональным свойством b :
function f( = ): void < // . >f() // ok, по умолчанию b = 0 f() // ok, по умолчанию , что также подразумевает b = 0 f(<>) // ошибка, свойство 'a' в таком случае требуется задать
Используйте деструктурирование с осторожностью. Как показал предыдущий пример, все сложные выражения деструктурирования имеют много ньюансов. В особенности это касается многоуровневого вложенного деструктурирования, которое действительно сложно для понимания даже без переименования, значений по умолчанию и аннотаций типа. Старайтесь оставлять выражения деструктурирования маленькими и простыми.
Поддержите перевод документации:
Поддерживатель | Github Репозиторий
Documentation generated by mdoc.
TypeScript: Переменные
В этом уроке мы узнаем, чем отличаются TypeScript и JavaScript в плане работы с переменными. Мы разберем, что такое вывод типов и зачем это нужно в программировании. Также узнаем, почему в TypeScript можно не указывать вручную тип переменных.
Вывод типов
Переменные и константы в TypeScript определяются так же, как и в JavaScript:
let age = 10; let company = 'Hexlet'; let user = < firstName: 'Miro', >; let fruits = ['apple', 'banana'];
При этом TypeScript выполняет дополнительную работу на фоне. Он автоматически связывает переменную или константу с типом данных начального значения. В программировании такой процесс называется выводом типов.
Тип переменной поменяться не может:
let age = 10; // Все нормально, тип тот же (Number) age = 11.1; // Type 'string' is not assignable to type 'number'. age = 'some string'; // Error!
Если мы попытаемся передать эту переменную в метод, который ожидает другой тип, то это тоже приведет к ошибке:
// Argument of type 'number' is not assignable to parameter // of type '(substring: string, . args: any[]) 'hexlet'.replace('xl', age);
Статическая типизация накладывает ограничение на массивы. Внутри могут храниться данные только одного типа:
let items = [1, 2, 3]; items.push(4); // Все хорошо // Argument of type 'string' is not assignable to parameter of type 'number'. items.push('code-basics'); // Error!
С объектами ситуация еще строже. В TypeScript нельзя не только менять тип свойств внутри объекта, но и добавлять новые свойства динамически:
let user = < firstName: 'Miro', >; // Property 'lastName' does not exist on type '< firstName: string; >'. user.lastName = 'Smith';
Явное указание типа
TypeScript позволяет явно указывать тип переменных. Но на практике это редко нужно делать вручную, так как вывод типов работает автоматически:
let name: string = 'Alice'; const count: number = 100; let canPlay: boolean = true;
Null
По умолчанию в TypeScript переменные могут содержать только указанный тип без исключений, например, мы не можем присвоить null:
let age = 30; age = null; // Error!
Такое поведение защищает нас от большого числа ошибок, которые связаны с тем, что нет проверок на null. При этом null иногда является допустимым значением. В этом случае используется специальный Union Type:
let age: number | null = 30; age = null;
Здесь мы указали, что тип у переменной age — это number | null . Читается это так: «число или null».
Union Type — интересная и удобная концепция, которую мы рассмотрим подробнее позже.
Выводы
В этом уроке мы познакомились с переменным в TypeScript. Мы узнали, чем в плане работы с ними TypeScript отличается от JavaScript. А также разобрались, почему в TypeScript можно не указывать вручную тип переменных.
Задание
Допишите тело функции repeat() , которая повторяет строку указанное количество раз. Функция должна возвращать полученный результат. Постарайтесь не использовать встроенные методы, для такой реализации вам понадобится цикл.
repeat('hexlet', 2); // hexlethexlet repeat('wo', 3); // wowowo
Упражнение не проходит проверку — что делать?
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Мой код отличается от решения учителя
Это нормально , в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Прочитал урок — ничего не понятно
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Полезное
- Метод String.repeat()
- Официальная документация
Основы TypeScript
Для хранения данных в программе в TypeScript, как и во многих языках программирования используются переменные.
Для определения переменных, как в JavaScript, можно использовать ключевое слово var :
var z; // переменная z
Другой способ определения переменной применяет ключевое слово let , которое было добавлено в JavaScript в стандарте ES 2015:
let z;
Применение let является более предпочтительным, поскольку позволяет избежать ряд проблем, связанных с объявлением переменных. В частности, с помощью var мы можем определить два и более раз переменную с одним и тем же именем:
var x = "hello"; console.log(x); var x = "work"; console.log(x);
Если программа большая, то мы можем не уследить за тем, что такая переменная уже объявлена, что является источником потенциальных ошибок. Подобную проблему позволяет решить let:
let x = "hello"; console.log(x); let x = "work"; // здесь будет ошибка, так как переменная x уже объявлена console.log(x);
То же самое относится, если переменная с одним и тем же именем определяется два раза, но с помощью обоих ключевых слов — let и var :
let x = "hello"; console.log(x); var x = "work"; // здесь будет ошибка, так как переменная x уже объявлена console.log(x);
Определив переменную, мы можем установить ее значение и в процессе работы программы поменять его на другое:
let z = 6; z = 8;
Кроме переменных в TypeScript имеются константы — для них можно установить значение только один раз. И далее процессе работы программы мы уже не сможем изменить это значение. Для определение констант используется ключевое слово const :
const z = 6; z = 8; // здесь ошибка - нельзя изменить значение константы z
Область видимости переменной и констант
Выше было сказано, что нельзя определить с помощью let определить более одного раза переменную (а с помощью const — константу) с одним и тем же именем. Данное утверждение относится к ситуации, когда переменные let и константы определяются в одной области видимости. Есть локальная область видимости, которая опреляется блоком кода, ограниченным фигурными скобками < //. код >. И есть глобальная область видимости — вне какого-либо блока кода.
Переменные и константы во вложенной области видимости могут скрывать переменные и константы с тем же именем, определенные во внешней области видимости. Например:
let x = 10; < let x = 25; console.log(x); // 25 >console.log(x); // 10
Здесь определены две переменных с одним и тем же именем. Но ошибки нет, поскольку они определены в разных областях видимости. Переменная let x = 10 определена в глобальной области видимости. А переменная let x = 25; определена во вложенном блоке кода — в локальной области видимости. И она скрывает внешнюю глобальную переменную. Поэтому консоль браузера выведет нам следующее:
25 10
То же самое относится к случаю, если переменные определяются в локальных областях видимости, которые вложены одна в другую:
let x = 10; < let x = 25; < let x = 163; console.log(x); // 163 >console.log(x); // 25 > console.log(x); // 10
Различия между var и let/const
Можно следующим образом суммировать различия между var и let/const:
Может быть доступна вне области видимости, в которой она определена.
< var x = 94; >console.log(x); // норм
Доступна только в рамках области видимости, в котором она определена
< let x = 94; >console.log(x); // ! Ошибка
Можно использовать в функции перед определением.
console.log(x); // undefined, но норм var x = 76;
Можно использовать только после определения.
console.log(x); // ! Ошибка let x = 76;
В одной и той же области видимости можно несколько раз определить переменную с одним и тем же именем.
var x = 72; console.log(x); // 72 var x = 24; // норм console.log(x); // 24
В одной и той же области видимости можно только один раз определить переменную с одним и тем же именем.
let x = 72; console.log(x); // 72 let x = 24; // ! Ошибка console.log(x);
Как можно объявить переменную в javascript typescript
В Javascript ключевое слово var относится к функциональной области видимости. Этим javascript отличается от многих языков программирования (C# / Java и т. д.), в которых переменные имеют блочную область видимости. Если представить блочную видимость в JavaScript, то в примере ниже мы ожидаем, что в консоль попадет 123 , однако вместо этого будет выведено 456 :
1 2 3 4 5
var foo = 123; if (true) var foo = 456; > console.log(foo); // 456
Так происходит, потому что < не создает новую область видимости переменных. Переменная foo , которая находится внутри if, ведет себя так же, как если бы она была объявлена вне его. Это поведение часто является источником ошибок в JavaScript. По этой причине TypeScript (и ES6) добавили ключевое слово let , которое позволяет нам объявлять переменные с настоящей блочной областью видимости.
Таким образом, если вы используете ключевое слово let вместо var , вы получите по настоящему уникальный элемент, который не будет взаимодействовать с областями видимости, которые располагаются вне его области видимости. Ниже мы переписали предыдущий пример, используя let :
1 2 3 4 5
let foo = 123; if (true) let foo = 456; > console.log(foo); // 123
Еще один пример, где let может защитить вас от ошибок.
1 2 3 4 5 6
var index = 0; var array = [1, 2, 3]; for (let index = 0; index array.length; index++) console.log(array[index]); > console.log(index); // 0
Честно говоря, мы считаем, что лучше использовать let , когда это возможно, так как это приводит к меньшим сюрпризам для новых и существующих разработчиков.
Функции создают новую область видимости¶
Поскольку мы упоминали об этом, мы хотели бы продемонстрировать, что функции создают новую область видимости переменных в JavaScript:
1 2 3 4 5 6
var foo = 123; function test() var foo = 456; > test(); console.log(foo); // 123
Это ведет себя так, как вы ожидаете. Без этого было бы очень сложно писать код на JavaScript.
Сгенерированный JS¶
JS код, генерируемый TypeScript, является простым переименованием переменной let , если подобное имя уже существует в окружающей области видимости. Например. следующий код генерируется как есть с простой заменой var на let :
1 2 3 4 5 6 7 8 9
if (true) let foo = 123; > // становится // if (true) var foo = 123; >
Однако, если имя уже используется в окружающей области видимости, то typescript сгенерирует следующий код:
1 2 3 4 5 6 7 8 9 10 11
var foo = '123'; if (true) let foo = 123; > // становится // var foo = '123'; if (true) var foo_1 = 123; // просто переименовываем >
Switch¶
Мы можем обернуть тело case , используя <> для переиспользования имени переменной в других case выражениях, как показано ниже:
1 2 3 4 5 6 7 8 9 10 11 12
switch (name) case 'x': let x = 5; // . break; > case 'y': let x = 10; // . break; > >
let в замыканиях¶
Самый частый вопрос на собеседовани — какой будет результат при исполнении следующего кода:
1 2 3 4 5 6 7 8 9 10 11
var funcs = []; // создаем список функций for (var i = 0; i 3; i++) funcs.push(function () console.log(i); >); > // вызываем их for (var j = 0; j 3; j++) funcs[j](); >
Начинающие разработчики часто говорят, что результатом будет 0,1,2 . Однако очень сильно удивляются, когда узнают, что этот ответ неправильный, а результатом исполнения функции будет 3 во всех трех функциях. Так происходит, потому что все эти функции выводят значение переменной i из окружающей области видимости, в момент вызова этих функций она равняется 3.
Одним из возможных вариантов исправления этой ошибки является создание функции для каждой итерации цикла. Как мы узнали ранее, мы можем создать новую область видимости с помощью немедленно вызывающейся функции (паттерн IIFE (function() < /* body */ >)(); ):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
var funcs = []; // создаем список функций for (var i = 0; i 3; i++) (function () var local = i; funcs.push(function () console.log(local); >); >)(); > // вызываем их for (var j = 0; j 3; j++) funcs[j](); >
В этом примере функция замыкает (поэтому ее и называют замыкание ) локальные переменные (по соглашению называемые local ) и использует их вместо i , которая используется в цикле.
Обратите внимание, что замыкания влияют на производительность (но они нам нужны для сохранения окружающего состояния).
В ES6 ключевое слово let в цикле будет иметь такое же поведение, как и в примере ранее:
1 2 3 4 5 6 7 8 9 10 11 12
var funcs = []; // создаем список инструкций for (let i = 0; i 3; i++) // используем let funcs.push(function () console.log(i); >); > // вызываем for (var j = 0; j 3; j++) funcs[j](); >
Использование let вместо var создает переменную i , уникальную для каждой итерации цикла.
Резюме¶
let — нереально полезный инструмент, который рекомендуется использовать как можно чаще. Он может существено повысить читабельность и предсказуемость вашего кода и снизить количество допускаемых ошибок.