[Перевод] Обзор базовых возможностей ES6

JavaScript сильно изменился за последние годы. Вот 12 новых возможностей, которые можно начать использовать уже сегодня!


История

Новые добавления в язык называются ECMAScript 6. Или ES6 или ES2015+.


С момента появления в 1995, JavaScript развивался медленно. Новые возможности добавлялись каждые несколько лет. ECMAScript появился в 1997, его целью было направить развитие JavaScript в нужное русло. Выходили новые версии — ES3, ES5, ES6 и так далее.


504a78cba9324062bb3d66d6af2e0588.png


Как видите, между версиями ES3, ES5 и ES6 есть пропуски длиной в 10 и 6 лет. Новая модель — делать маленькие изменения каждый год. Вместо того, чтобы накопить огромное количество изменений и выпустить их все за раз, как это было с ES6.


Browsers Support

Все современные браузеры и среды исполнения уже поддерживают ES6!


581ded906b3340ae91209ab6f200999e.png


Chrome, MS Edge, Firefox, Safari, Node и многие другие системы имеют встроенную поддержку большинства возможностей JavaScript ES6. Так что, все из этого пособия можно использовать прямо сейчас.


Поехали!


Главные возможности ES6

Все сниппеты можно вставлять в консоль браузера и запускать.


Block scope variables


В ES6 мы перешли от varк let/const.


Что не так с var?


Проблема var в том, что переменная «протекает» в другие блоки кода, такие как циклы for или блоки условий if:


ES5
var x = 'outer';
function test(inner) {
  if (inner) {
    var x = 'inner'; // scope whole function
    return x;
  }
  return x; // gets redefined on line 4
}

test(false); // undefined 
test(true); // inner

В строке test(false) можно ожидать возврат outer, но нет, мы получаем undefined. Почему?


Потому что даже не смотря на то, что блок if не выполняется, на 4й строке происходит переопределение var x как undefined.


ES6 спешит на помощь:


ES6
let x = 'outer';
function test(inner) {
  if (inner) {
    let x = 'inner';
    return x;
  }
  return x; // gets result from line 1 as expected
}

test(false); // outer
test(true); // inner

Изменив var на let мы откорректировали поведение. Если блок if не вызывается, то переменная x не переопределяется.


IIFE (immediately invoked function expression)


Давайте сначала рассмотрим пример:


ES5
{
  var private = 1;
}

console.log(private); // 1

Как видите,  private протекает наружу. Нужно использовать IIFE (immediately-invoked function expression):


ES5
(function(){
  var private2 = 1;
})();

console.log(private2); // Uncaught ReferenceError

Если взглянуть на jQuery/lodash или любые другие проекты с открытым исходным кодом, то можно заметить, что там IIFE используется для содержания глобальной среды в чистоте. А глобальные штуки определяются со специальными символами вроде _,  $ или jQuery.


В ES6 не нужно использовать IIFE, достаточно использовать блоки и let:


ES6
{
  let private3 = 1;
}

console.log(private3); // Uncaught ReferenceError

Const


Можно также использовать const если переменная не должна изменяться.


Итог:


  • забудьте var, используйте let и const.
  • Используйте const для всех референсов; не используйте var.
  • Если референсы нужно переопределять, используйте let вместо const.

Template Literals


Не нужно больше делать вложенную конкатенацию, можно использовать шаблоны. Посмотрите:


ES5
var first = 'Adrian';
var last = 'Mejia';
console.log('Your name is ' + first + ' ' + last + '.');

С помощью бэктика () и интерполяции строк ${}` можно сделать так:


ES6
const first = 'Adrian';
const last = 'Mejia';
console.log(`Your name is ${first} ${last}.`);

Multi-line strings


Не нужно больше конкатенировать строки с + \n:


ES5
var template = '
  • \n' + '
    \n' + ' \n' + ' \n' + ' \n' + '
    \n' + ' \n' + '
  • '; console.log(template);

    В ES6 можно снова использовать бэктики:


    ES6
    const template = `
  • `; console.log(template);

    Оба блока кода генерируют одинаковый результат


    Destructuring Assignment


    ES6 desctructing — полезная и лаконичная штука. Посмотрите на примеры:


    Получение элемента из массива


    ES5
    var array = [1, 2, 3, 4];
    
    var first = array[0];
    var third = array[2];
    
    console.log(first, third); // 1 3

    То же самое:


    ES6
    const array = [1, 2, 3, 4];
    
    const [first, ,third] = array;
    
    console.log(first, third); // 1 3

    Обмен значениями


    ES5
    var a = 1;
    var b = 2;
    
    var tmp = a;
    a = b;
    b = tmp;
    
    console.log(a, b); // 2 1

    То же самое:


    ES6
    let a = 1;
    let b = 2;
    
    [a, b] = [b, a];
    
    console.log(a, b); // 2 1

    Деструктуризация нескольких возвращаемых значений


    ES5
    function margin() {
      var left=1, right=2, top=3, bottom=4;
      return { left: left, right: right, top: top, bottom: bottom };
    }
    
    var data = margin();
    var left = data.left;
    var bottom = data.bottom;
    
    console.log(left, bottom); // 1 4

    В строке 3 можно вернуть в виде массива:


    return [left, right, top, bottom];

    но вызывающему коду придется знать о порядке данных.


    var left = data[0];
    var bottom = data[3];

    С ES6 вызывающий выбирает только нужные данные (строка 6):


    ES6
    function margin() {
      const left=1, right=2, top=3, bottom=4;
      return { left, right, top, bottom };
    }
    
    const { left, bottom } = margin();
    
    console.log(left, bottom); // 1 4

    Заметка:  В строке 3 содержатся другие возможности ES6. Можно сократить { left: left } до { left }. Смотрите, насколько это лаконичнее по сравнению с версией ES5. Круто же?


    Деструктуризация и сопоставление параметров


    ES5
    var user = {firstName: 'Adrian', lastName: 'Mejia'};
    
    function getFullName(user) {
      var firstName = user.firstName;
      var lastName = user.lastName;
    
      return firstName + ' ' + lastName;
    }
    
    console.log(getFullName(user)); // Adrian Mejia

    То же самое (но короче):


    ES6
    const user = {firstName: 'Adrian', lastName: 'Mejia'};
    
    function getFullName({ firstName, lastName }) {
      return `${firstName} ${lastName}`;
    }
    
    console.log(getFullName(user)); // Adrian Mejia

    Глубокое сопоставление


    ES5
    function settings() {
      return { display: { color: 'red' }, keyboard: { layout: 'querty'} };
    }
    
    var tmp = settings();
    var displayColor = tmp.display.color;
    var keyboardLayout = tmp.keyboard.layout;
    
    console.log(displayColor, keyboardLayout); // red querty

    То же самое (но короче):


    ES6
    function settings() {
      return { display: { color: 'red' }, keyboard: { layout: 'querty'} };
    }
    
    const { display: { color: displayColor }, keyboard: { layout: keyboardLayout }} = settings();
    
    console.log(displayColor, keyboardLayout); // red querty

    Это также называют деструктуризацией объекта (object destructing).


    Как видите, деструктуризация может быть очень полезной и может подталкивать к улучшению стиля кодирования.


    Советы:


    • Используйте деструктуризацию для получения элементов из массива и для обмена значениями. Не нужно делать временные референсы — сэкономите время.
    • Не используйте деструктуризацию массива для нескольких возвращаемых значений, вместо этого используйте деструктуризацию объекта.

    Классы и объекты


    В ECMAScript 6 мы перешли от «функций-конструкторов» к «классам» .


    Каждый объект в JavaScript имеет прототип, который является другим объектом. Все объекты в JavaScript наследуют методы и свойства от своего прототипа.

    В ES5 объектно-ориентированное программирование достигалось с помощью функций-конструкторов. Они создавали объекты следующим образом:


    ES5
    var Animal = (function () {
      function MyConstructor(name) {
        this.name = name;
      }
      MyConstructor.prototype.speak = function speak() {
        console.log(this.name + ' makes a noise.');
      };
      return MyConstructor;
    })();
    
    var animal = new Animal('animal');
    animal.speak(); // animal makes a noise.

    В ES6 есть новый синтаксический сахар. Можно сделать то же самое с меньшим кодом и с использованием ключевых слов class и construсtor. Также заметьте, как четко определяются методы:  construсtor.prototype.speak = function () vs speak():


    ES6
    class Animal {
      constructor(name) {
        this.name = name;
      }
      speak() {
        console.log(this.name + ' makes a noise.');
      }
    }
    
    const animal = new Animal('animal');
    animal.speak(); // animal makes a noise.

    Оба стиля (ES5/6) дают одинаковый результат.


    Советы:


    • Всегда используйте синтаксис class и не изменяйте prototype напрямую. Код будет лаконичнее и его будет легче понять.
    • Избегайте создания пустого конструктора. У классов есть конструктор по умолчанию если не задать собственный.

    Наследование


    Давайте продолжим предыдущий пример с классом Animal. Допустим, нам нужен новый класс Lion.


    В ES5 придется немного поработать с прототипным наследованием.


    ES5
    var Lion = (function () {
      function MyConstructor(name){
        Animal.call(this, name);
      }
    
      // prototypal inheritance
      MyConstructor.prototype = Object.create(Animal.prototype);
      MyConstructor.prototype.constructor = Animal;
    
      MyConstructor.prototype.speak = function speak() {
        Animal.prototype.speak.call(this);
        console.log(this.name + ' roars ');
      };
      return MyConstructor;
    })();
    
    var lion = new Lion('Simba');
    lion.speak(); // Simba makes a noise.
    // Simba roars.

    Не будем вдаваться в детали, но заметьте несколько деталей:


    • Строка 3, напрямую вызываем конструкторAnimal с параметрами.
    • Строки 7–8, назначаем прототип Lion прототипом класса Animal.
    • Строка 11, вызываем метод speak из родительского класса Animal.

    В ES6 есть новые ключевые слова extends и super.


    ES6
    class Lion extends Animal {
      speak() {
        super.speak();
        console.log(this.name + ' roars ');
      }
    }
    
    const lion = new Lion('Simba');
    lion.speak(); // Simba makes a noise.
    // Simba roars.

    Посмотрите, насколько лучше выглядит код на ES6 по сравнению с ES5. И они делают одно и то же! Win!


    Совет:


    • Используйте встроенный способ наследования — extends.

    Нативные промисы


    Переходим от callback hell к промисам (promises)


    ES5
    function printAfterTimeout(string, timeout, done){
      setTimeout(function(){
        done(string);
      }, timeout);
    }
    
    printAfterTimeout('Hello ', 2e3, function(result){
      console.log(result);
    
      // nested callback
      printAfterTimeout(result + 'Reader', 2e3, function(result){
        console.log(result);
      });
    });

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


    Все становится совсем плохо когда нужно добавить третий или четвертый коллбек. Давайте посмотрим, что можно сделать с промисами:


    ES6
    function printAfterTimeout(string, timeout){
      return new Promise((resolve, reject) => {
        setTimeout(function(){
          resolve(string);
        }, timeout);
      });
    }
    
    printAfterTimeout('Hello ', 2e3).then((result) => {
      console.log(result);
      return printAfterTimeout(result + 'Reader', 2e3);
    
    }).then((result) => {
      console.log(result);
    });

    С помощью then можно обойтись без вложенных функций.


    Стрелочные функции


    В ES5 обычные определения функций не исчезли, но был добавлен новый формат — стрелочные функции.


    В ES5 есть проблемы с this:


    ES5
    var _this = this; // need to hold a reference
    
    $('.btn').click(function(event){
      _this.sendData(); // reference outer this
    });
    
    $('.input').on('change',function(event){
      this.sendData(); // reference outer this
    }.bind(this)); // bind to outer this

    Нужно использовать временный this чтобы ссылаться на него внутри функции или использовать bind. В ES6 можно просто использовать стрелочную функцию!


    ES6
    // this will reference the outer one
    $('.btn').click((event) =>  this.sendData());
    
    // implicit returns
    const ids = [291, 288, 984];
    const messages = ids.map(value => `ID is ${value}`);

    For…of


    От for переходим к forEach, а потом к for...of:


    ES5
    // for
    var array = ['a', 'b', 'c', 'd'];
    for (var i = 0; i < array.length; i++) {
      var element = array[i];
      console.log(element);
    }
    
    // forEach
    array.forEach(function (element) {
      console.log(element);
    });

    ES6 for…of позволяет использовать итераторы


    ES6
    // for ...of
    const array = ['a', 'b', 'c', 'd'];
    for (const element of array) {
        console.log(element);
    }

    Параметры по умолчанию


    От проверки параметров переходим к параметрам по умолчанию. Вы делали что-нибудь такое раньше?


    ES5
    function point(x, y, isFlag){
      x = x || 0;
      y = y || -1;
      isFlag = isFlag || true;
      console.log(x,y, isFlag);
    }
    
    point(0, 0) // 0 -1 true 
    point(0, 0, false) // 0 -1 true 
    point(1) // 1 -1 true
    point() // 0 -1 true

    Скорее всего да. Это распространенный паттерн проверки наличия значения переменной. Но тут есть некоторые проблемы:


    • Строка 8, передаем 0, 0 получаем 0, -1
    • Строка 9, передаем false, но получаем true.

    Если параметр по умолчанию это булева переменная или если задать значение 0, то ничего не получится. Почему? Расскажу после этого примера с ES6;)


    В ES6 все получается лучше с меньшим количеством кода:


    ES6
    function point(x = 0, y = -1, isFlag = true){
      console.log(x,y, isFlag);
    }
    
    point(0, 0) // 0 0 true
    point(0, 0, false) // 0 0 false
    point(1) // 1 -1 true
    point() // 0 -1 true

    Получаем ожидаемый результат. Пример в ES5 не работал. Нужно проверять на undefined так как false,  null,  undefined и 0 — это все falsy-значения. С числами можно так:


    ES5
    function point(x, y, isFlag){
      x = x || 0;
      y = typeof(y) === 'undefined' ? -1 : y;
      isFlag = typeof(isFlag) === 'undefined' ? true : isFlag;
      console.log(x,y, isFlag);
    }
    
    point(0, 0) // 0 0 true
    point(0, 0, false) // 0 0 false
    point(1) // 1 -1 true
    point() // 0 -1 true

    С проверкой на undefined все работает как нужно.


    Rest-параметры


    От аргументов к rest-параметрам и операции spread.


    В ES5 работать с переменным количеством аргументов неудобно.


    ES5
    function printf(format) {
      var params = [].slice.call(arguments, 1);
      console.log('params: ', params);
      console.log('format: ', format);
    }
    
    printf('%s %d %.2f', 'adrian', 321, Math.PI);

    С rest  ... все намного проще.


    ES6
    function printf(format, ...params) {
      console.log('params: ', params);
      console.log('format: ', format);
    }
    
    printf('%s %d %.2f', 'adrian', 321, Math.PI);

    Операция Spread


    Переходим от apply() к spread. Опять же,  ... спешит на помощь:


    Помните: мы используем apply() чтобы превратить массив в список аргументов. например,  Math.max() принимает список параметров, но если у нас есть массив, то можно использовать apply.

    ES5
    Math.max.apply(Math, [2,100,1,6,43]) // 100

    В ES6 используем spread:


    ES6
    Math.max(...[2,100,1,6,43]) // 100

    Мы также перешли от concat к spread’у:


    ES5
    var array1 = [2,100,1,6,43];
    var array2 = ['a', 'b', 'c', 'd'];
    var array3 = [false, true, null, undefined];
    
    console.log(array1.concat(array2, array3));

    В ES6:


    ES6
    const array1 = [2,100,1,6,43];
    const array2 = ['a', 'b', 'c', 'd'];
    const array3 = [false, true, null, undefined];
    
    console.log([...array1, ...array2, ...array3]);

    Заключение

    JavaScript сильно изменился. Эта статья покрывает только базовые возможности, о которых должен знать каждый разработчик.

    Комментарии (1)

    • 25 октября 2016 в 12:44

      –1

      Кстати, наши онлайн-курсы учат новому стандарту с самого начала, с «основ программирования» :-)

    © Habrahabr.ru