Если у вас был опыт в написании JS приложений, то наверняка вы знакомы с его синтаксисом. Он весьма не обычен и имеет ряд причуд, но в целом обдуманный, понятный и имеющий множество сходств с другими языками.
ES6 добавляет несколько новых синтаксических форм с которыми нам предстоит познакомиться. В этой главе мы пробежимся по многим из них и узнаем, что же нового у нас в арсенале.
Обратите внимание: На момент написание данной книги, большинство новых возможностей ES6 уже были имплементированы как популярными браузерами(Firefox, Chrome и т.п.), так и множеством интересных окружений. Но к сожалению не все браузеры или же окружения могут работать с ES6. Как мы говорили в прошлой главе — транспилинг это наше все. С помощью данного подхода вы можете запустить любой из приведенных в этой книге примеров. Для этого в нашем распоряжении есть ряд инструментов — ES6Fiddle (http://www.es6fiddle.net/) отличная и простая в использовании площадка, для того, чтобы попробовать ES6 и REPL для Babel (http://babeljs.io/repl/).
Блочная область видимости
Наверняка вам известно, что фундаментальной единицей видимости в JavaScript, является функция (function). Если вам необходимо реализовать блок со своей областью видимости, то наиболее подходящий способ заключается в использовании IIFE (выражение немедленно вызывающейся функции), например:
var a = 2;
(function IIFE(){
var a = 3;
console.log( a ); // 3
})();
console.log( a ); // 2
Оператор let
Однако, теперь мы можем реализовать области видимость в любом виде блока и называется это — блочной областью видимости (block scoping). Все, что нам необходимо для создания новой области видимости — пара, всем давно известных фигурных скобок
{ .. }
. Вместо использования оператора var
, который мы использовали для объявления переменных внутренней (или глобальной) области видимости, мы можем использовать let
для объявления блочной:var a = 2;
{
let a = 3;
console.log( a ); // 3
}
console.log( a ); // 2
Конечно для JS это необычно, но для разработчиков которые работали с другими языками, данный паттерн давно известен.
Это лучший способ создания блочной видимости с использованием
{ ... }
. Я рекомендую вам, помещать объявления let
в самом начале блока и если у вас более одного объявления, использовать всего один оператор let
.Стилистически, я бы даже рекомендовал вам, делать объявление на одной строке с открывающимся
{
, чтобы явно подчеркнуть для какого блока предназначены эти переменные:{ let a = 2, b, c;
// ..
}
Сейчас это выглядит довольно странно и не совпадает с большинством рекомендаций из литературы по ES6, но у меня есть причины для моего безумия. (прим. перев.: узнаем чуть ниже).
Существует и другая предложенная форма оператора
let
, и называется она let-блок (let-block)://Данная форма не была включена в ES6
let (a = 2, b, c) {
// ..
}
Это отличный пример, того, что я называю "явная блочная область видимости", в то время как
let
схожий с var
— не явный, так как распространяется на весь блок области видимости. К сожалению форма
let (..) { .. }
, не была принята в ES6. Быть может в последующих обновлениях ES6, мы увидим эту форму, но пока первый пример, это лучшее, что у нас есть.Чтобы подчеркнуть неявную природу оператора
let
, давайте рассмотрим следующий пример:let a = 2;
if (a > 1) {
let b = a * 3;
console.log( b ); // 6
for (let i = a; i <= b; i++) {
let j = i + 10
console.log( j );
}
// 12 13 14 15 16
let c = a + b;
console.log( c ); // 8
}
А теперь небольшая викторинка, без повторного изучения кода, подскажите мне: какие переменные существуют в блоке
if
, а какие только в блоке цикла for
?Ответ: блок
if
содержит переменные a
и b
блочной области видимости, а for
— переменные i
и j
блочной области видимости.Не удивляет ли вас то, что переменная
i
не была добавлена в область видимости блока if
? В данном примере так же таится опасность в столь низком объявлении переменной
c
;Традиционный оператор
var
своего рода "прикрепляет" переменную к области видимости и инициализирует ее при входе в функцию еще до ее выполнения, в не зависимости от, того где она была объявлена. Данное явление известно как поднятием. Оператор let
, так же прикрепляет переменную к области видимости до ее выполнения, но не инициализирует ее при входе в функцию, что в случае попытки обращения к такой переменной, раньше чем она будет объявлена/инициализирована, приведет к ошибке.Наглядный пример:
{
console.log( a ); // undefined
console.log( b ); // ReferenceError!
var a;
let b;
}
Обратите внимание: ReferenceError который будет вызван ранним обращением к переменной, до того как она была объявлена или инициализирована, технически называется — ошибка TDZ (temporal dead zone).
Хочу обратить ваше внимание на, то, что «не инициализирована», не означает, что ей необходимо явно присвоить какое либо значение.
let b;
— вполне достаточно. Предполагается, что переменная который не было присвоено значение во время объявления, будет инициализирована значением undefined
, по умолчанию. По этому let b;
тоже самое, что и let b = undefined;
. Но повторюсь — до того как, выражение let b;
не будет выполнено, использовать данную переменную, вы не можете.Еще один неожиданный поворот заключается в поведении
typeof
с TDZ переменными:{
if (typeof a === "undefined") {
console.log( "cool" );
}
if (typeof b === "undefined") { // ReferenceError!
// ..
}
// ..
let b;
}
Так как переменная
а
не была объявлена, то единственный безопасным способом проверить ее на существование будет использование оператора typeof
. Но не тут-то было в случае с переменной b
, которая бросит исключение, так как она была объявлена намного ниже с помощью оператора let
.Я не просто рекомендую, а настаиваю на том, чтобы все объявления с использованием нового оператора
let
, были в самом верху блока. Это избавит вас от головной боли рефакторинга, устранения необъяснимых ошибок, сделает код более понятным и предсказуемым.Примечание: Больше об операторе
let
и блочной области видимости, мы поговорим в главе 3.let + for
Еще одной значительной особенностью оператора
let
, является то, как он себя ведет с циклом for
.Рассмотрим пример:
var funcs = [];
for (let i = 0; i < 5; i++) {
funcs.push( function(){
console.log( i );
} );
}
funcs[3](); // 3
Выражение
let i
в шапке for
объявляет новую переменную не для всего цикла, а для каждой отдельно взятой итерации. То же самое верно и для замыкания внутри цикла. По факту, код ведет себя в точности так, как и ожидается.Если вы попытаетесь вместо
let i
, использовать var i
, то вы бы получили в результате выполнения данного пример 5, а не 3, так как в таком случае замыкание будет общим для всех итераций.Немного добавив воды, можно получить следующий пример, который делает то же самое:
var funcs = [];
for (var i = 0; i < 5; i++) {
let j = i;
funcs.push( function(){
console.log( j );
} );
}
funcs[3](); // 3
В данном примере мы явно создаем новую переменную
j
для каждой итерации. Я предпочитаю первый вариант, который, конечно же, менее явный, но не настолько, чтобы от него отказаться.Оператор const
У нас имеется еще один оператор для рассмотрения, также относящийся к блочной области видимости —
const
.Константа, это такая переменная которая после первичной инициализации доступна только для чтения.
Например:
{
const a = 2;
console.log( a ); // 2
a = 3; // TypeError!
}
Вы не можете поменять значение переменной, которой уже было присвоено значение во время объявления. При объявлении константы, вы должны явно указывать значение переменной. По умолчанию константа не получает значение
undefined
, следовательно если вам понадобится такое значение, то вам придется вручную его присвоить — const a = undefined;
.Константы имеют ограничения на присвоения, но нет никаких ограничений связанных со значение переменной. Например если константой является комплексное значение, то вы по-прежнему можете его модифицировать:
{
const a = [1,2,3];
a.push( 4 );
console.log( a ); // [1,2,3,4]
a = 42; // TypeError!
}
Таким образом, константа своего рода держит константную ссылку, но массив на который она указывает — по-прежнему динамичен.
Обратите внимание: Так как мы не можем удалять ссылки, присвоение объекта или массива константе, означает, что значение не сможет быть удалено сборщиком мусора до тех пор, пока не покинет лексическую область видимости. Конечно это может быть желательным поведением, но будьте осторожны.
Раньше мы использовали заглавные буквы в литералах которые предназначались переменным, тем самым намекая, что значения таких переменных менять не желательно. Сейчас же, мы можем использовать константы которые предостерегают нас от нежелательных изменений.
По слухам, так как использование константы, дает понять движку JS, что значение никогда не будет изменено, сделает данный оператор более оптимизированным решением нежели
let
или var
.Не смотря на наши фантазии по этому поводу, намного важней, чтобы вы понимали когда вам действительно необходимо использование константы. Не стоит использовать константы для простых переменных, иначе это может стать причиной недопонимание.
Блочная область видимости функций
Начиная с ES6, подразумевается, что функции которые были объявлены в блоке, буду иметь его область видимости (блочную). До ES6, спецификация умалчивала по этому поводу, в отличии от различных конкретных реализации. Тем не менее, сейчас спецификация соответствует реальности.
Рассмотрим пример:
{
foo(); // работает!
function foo() {
// ..
}
}
foo(); // ReferenceError
В данном примере, функция объявлена в блочной области видимости, следовательно она не доступна из вне. Стоит так же отметить интересный факт — в отличии от объявление
let
, с которым мы не можем работать до того, как такая переменная будет проиницилизированна, в данном случае ошибки TDZ не возникает.Блочная область видимости функций может стать для вас проблемой, если вы до сих пор пишете код как в приведенном ниже примере, рассчитывая на старое поведение:
if (something) {
function foo() {
console.log( "1" );
}
}
else {
function foo() {
console.log( "2" );
}
}
foo(); // ??
В пред-ES6 окружениях, результатом вызова функции
foo()
будет "2"
— в независимости от значения переменной something
, так как благодаря поднятию рабочей функцией будет только вторая.В ES6 последняя строчка бросит исключение —
ReferenceError
.
Комментарии (22)
grep0
14.05.2015 06:20-7Ура, жаваскрипт превращается в приличный язык! Надеюсь, «var» и «function foo()» задепрекейтят в следующей версии
noder
14.05.2015 07:50+2А что не так с function foo()?
some_x
14.05.2015 10:44this не захватывает
MuLLtiQ
14.05.2015 22:05var obj = { _x: 42, getX: () => this._x }; console.log(obj.getX());
Что вы ожидаете здесь увидеть в консоли? :)some_x
28.05.2015 13:36Ошибку компилятора
Property '_x' does not exist on type 'Item'.MuLLtiQ
28.05.2015 15:24Какой компилятор скажет вам это?
babel на выходе генерирует это
код"use strict"; var obj = { _x: 42, getX: function getX() { return undefined._x; } }; console.log(obj.getX());
auine Автор
28.05.2015 15:55babeljs.io/docs/faq/#why-is-this-being-remapped-to-undefined-
На всякий случайauine Автор
28.05.2015 16:01
some_x
28.05.2015 21:42typescript, с натяжкой, но его всё же можно рассматривать как компилятор es6 -> es5
TheShock
28.05.2015 14:30Что вы ожидаете здесь увидеть в консоли? :)
Мне вообще не хотелось бы, чтобы подобные объекты создавались прям в методеMuLLtiQ
28.05.2015 15:20И не должны — это очевидная ошибка. «стрелка» не должна использоваться для определения методов, это всего лишь короткий вариант, который должен использоваться для вызова функций «а-ля функсьональ»
noder
14.05.2015 08:26+1Чесно говоря не разделяю большинства опасений автора касательно оператора let. У большинства ЯП такое-же поведение при использовании переменной до ее объявления или же при использовании переменной, объявленной в шапке for после самого цикла.
TheShock
скажите:
1. const должен работать как let или var?
2. const как свойство класса использовать нельзя, я тикер понимаю
3. Я в этих примерах что-то никогда не видел, есть ли модификатор static?
nd0ut
1. Как let
2. Нельзя
3. Есть, но только для методов и полей класса
TheShock
1. Замечательно
2. Плохо
3. Замечательно)
Интересно, чисто логически, будет ли корректно работать?
И даже так?
Хотя и было сказано:
Но я не совсем согласен.
auine Автор
Первый пример разумеется работоспособный, но какого либо преимущества над использованием обычного оператора
var
нет. Но если вы напишите например такое:То отхватите ошибку дублирования.
Второй же содержит ошибку, так как константа
i
— read-only, и не может быть инкрементирована или деинкрементированаTheShock
Вообще в идеологии let, как я понимаю, нету разницы между первым и вторым примером, т.е. let в for распространяется на каждый новый внутренний блок и потому, как я показал в примере ниже — в Хроме на каждой итерации создается своя константа и не блокируется предыдущей read-only. Такой подход сделано, чтобы корректно работал следующий скрипт, который корректно работает в Хроме, и пока некорректно (как var) в Файрфоксе:
Думаю, когда в Файрфоксе настроят корректную видимость let (чтобы для каждого вызова он был независим — можно будет в объявлении for использовать const)
TheShock
В Хроме и Файрфоксе это работает правильно (0, 3, 6, 9, 12)
Следующее в Файрфоксе вызывает «invalid assignment to const i», а в хроме работает корректно с «use strict» и уходит в бесконечный цикл без «use strict»
auine Автор
Почему то, мне кажется, что нового создания переменной не происходит, а лишь передача значения (хотя в виде переменной) в scope конкретной итерации.
А с
const
уходит в бесконечный так как поведение хрома достаточно интересно. Он не бросает каких либо исключений, а просто игнорирует присвоение, инкремент.