Если у вас был опыт в написании 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)


  1. TheShock
    14.05.2015 00:31

    скажите:
    1. const должен работать как let или var?
    2. const как свойство класса использовать нельзя, я тикер понимаю
    3. Я в этих примерах что-то никогда не видел, есть ли модификатор static?


    1. nd0ut
      14.05.2015 01:18

      1. Как let
      2. Нельзя
      3. Есть, но только для методов и полей класса


      1. TheShock
        14.05.2015 10:46

        1. Замечательно
        2. Плохо
        3. Замечательно)

        Интересно, чисто логически, будет ли корректно работать?

        for (let i = 0; i < 5; i++) {
          const x = i * 17;
        }
        


        И даже так?

        for (const i = 0; i < 5; i++) {
        
        }
        


        Хотя и было сказано:

        Не смотря на наши фантазии по этому поводу, намного важней, чтобы вы понимали когда вам действительно необходимо использование константы. Не стоит использовать константы для простых переменных, иначе это может стать причиной недопонимание.


        Но я не совсем согласен.


        1. auine Автор
          14.05.2015 13:36

          Первый пример разумеется работоспособный, но какого либо преимущества над использованием обычного оператора var нет. Но если вы напишите например такое:

          const a = 10;
          const a = 11;
          

          То отхватите ошибку дублирования.
          Второй же содержит ошибку, так как константа iread-only, и не может быть инкрементирована или деинкрементирована


          1. TheShock
            14.05.2015 13:53

            Вообще в идеологии let, как я понимаю, нету разницы между первым и вторым примером, т.е. let в for распространяется на каждый новый внутренний блок и потому, как я показал в примере ниже — в Хроме на каждой итерации создается своя константа и не блокируется предыдущей read-only. Такой подход сделано, чтобы корректно работал следующий скрипт, который корректно работает в Хроме, и пока некорректно (как var) в Файрфоксе:

            (function () {
             'use strict';
            
             for (let i = 0; i < 5; i++) {
              setTimeout( function () {
                console.log(i);
              }, i );
             }
            
            })();
            


            Думаю, когда в Файрфоксе настроят корректную видимость let (чтобы для каждого вызова он был независим — можно будет в объявлении for использовать const)


        1. TheShock
          14.05.2015 13:47

          В Хроме и Файрфоксе это работает правильно (0, 3, 6, 9, 12)

          (function () {
          
            'use strict';
            
            for (let i = 0; i < 5; i++) {
              const a = i * 3;
              console.log( a );
            }
          
          })()
          


          Следующее в Файрфоксе вызывает «invalid assignment to const i», а в хроме работает корректно с «use strict» и уходит в бесконечный цикл без «use strict»

          (function () {
          
            'use strict';
            
            for (const i = 0; i < 5; i++) {
              console.log( i );
            }
          
          })()
          


          1. auine Автор
            14.05.2015 14:08

            Почему то, мне кажется, что нового создания переменной не происходит, а лишь передача значения (хотя в виде переменной) в scope конкретной итерации.

            А с const уходит в бесконечный так как поведение хрома достаточно интересно. Он не бросает каких либо исключений, а просто игнорирует присвоение, инкремент.


  1. grep0
    14.05.2015 06:20
    -7

    Ура, жаваскрипт превращается в приличный язык! Надеюсь, «var» и «function foo()» задепрекейтят в следующей версии


    1. noder
      14.05.2015 07:50
      +2

      А что не так с function foo()?


      1. some_x
        14.05.2015 10:44

        this не захватывает


        1. MuLLtiQ
          14.05.2015 22:05

          var obj = {
            _x: 42,
            getX: () => this._x
          };
          console.log(obj.getX());
          


          Что вы ожидаете здесь увидеть в консоли? :)


          1. some_x
            28.05.2015 13:36

            Ошибку компилятора

            Property '_x' does not exist on type 'Item'.


            1. MuLLtiQ
              28.05.2015 15:24

              Какой компилятор скажет вам это?

              babel на выходе генерирует это

              код
              "use strict";
              
              var obj = {
                _x: 42,
                getX: function getX() {
                  return undefined._x;
                }
              };
              console.log(obj.getX());
              


              1. auine Автор
                28.05.2015 15:55


                1. MuLLtiQ
                  28.05.2015 16:28

                  Да я знаю как это работает, просто эйфории по-поводу «стрелки» не понимаю :)


              1. some_x
                28.05.2015 21:42

                typescript, с натяжкой, но его всё же можно рассматривать как компилятор es6 -> es5


          1. TheShock
            28.05.2015 14:30

            Что вы ожидаете здесь увидеть в консоли? :)

            Мне вообще не хотелось бы, чтобы подобные объекты создавались прям в методе


            1. MuLLtiQ
              28.05.2015 15:20

              И не должны — это очевидная ошибка. «стрелка» не должна использоваться для определения методов, это всего лишь короткий вариант, который должен использоваться для вызова функций «а-ля функсьональ»


  1. noder
    14.05.2015 08:26
    +1

    Чесно говоря не разделяю большинства опасений автора касательно оператора let. У большинства ЯП такое-же поведение при использовании переменной до ее объявления или же при использовании переменной, объявленной в шапке for после самого цикла.


  1. gro
    14.05.2015 13:41

    То есть в for-let на каждой итерации по лишнему замыканию создаётся?


    1. MuLLtiQ
      14.05.2015 22:06

      ну без «let» вам и так нужно будет создать замыкание с помощью вспомогательной функции