Pull to refresh

ES6 и за его пределами. Глава 2: Синтаксис. Часть 1

Reading time 6 min
Views 29K
Original author: Kyle Simpson и другие


Если у вас был опыт в написании 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.
Tags:
Hubs:
+22
Comments 23
Comments Comments 23

Articles