Что такое es6 javascript
Перейти к содержимому

Что такое es6 javascript

  • автор:

ES6 в деталях: генераторы

Мне не терпится вам всё рассказать. Сегодня мы будем обсуждать самую волшебную функциональность в ES6.

Что я имел в виду под словом «волшебную»? Во-первых, эта функциональность настолько отличается от всего того, что уже есть в JS, что поначалу может показаться колдовством. В том смысле, что она выворачивает обычное поведение языка наизнанку! Если это не магия, то я не знаю, что это.

Но не только поэтому. Возможности этой фичи по упрощению кода и устранению «ада колбеков» граничат со сверхъестественным.

Я излишне нахваливаю? Давайте углубимся, и вы сами рассудите.

Знакомьтесь, генераторы ES6

Что такое генераторы?

Начнём с рассмотрения одного генератора:

function* quips(name) < yield "привет, " + name + "!"; yield "я надеюсь, вам нравятся статьи"; if (name.startsWith("X")) < yield "как круто, что ваше имя начинается с X, " + name; > yield "увидимся!"; > 

Это часть кода для говорящей кошки, возможно, самого важного вида приложений в интернете на сегодняшний день. (Давайте, нажмите на ссылку, поиграйте с кошкой. Когда вы окончательно запутаетесь, возвращайтесь сюда за объяснением.)

Выглядит как-то похоже на функцию, верно? Это называется, функция-генератор, и у неё есть много общего с обычными функциями. Но вы можете заметить два отличия уже сейчас:

  • Обычные функции начинаются с function . Функции-генераторы начинаются с function* .
  • Внутри функции-генератора есть ключевое слово yield с синтаксисом, похожим на return . Отличие в том, что функция (в том числе функция-генератор) может вернуть значение только один раз, но отдать значение функция-генератор может любое количество раз. Выражение yield приостанавливает выполнение генератора, так что его можно позже возобновить.

Вот, именно в этом самая большая разница между обычными функциями и функциями-генераторами. Обычные функции не могут поставить себя на паузу. Функции-генераторы могут.

Что делают генераторы

Что произойдёт, если запустить функцию-генератор quips() ?

> var iter = quips("jorendorff"); [object Generator] > iter.next() < value: "привет, jorendorff!", done: false > > iter.next() < value: "я надеюсь, вам нравятся статьи", done: false > > iter.next() < value: "увидимся!", done: false > > iter.next() < value: undefined, done: true > 

Возможно, вы очень привыкли к обычным функциям и тому, как они себя ведут. Когда их вызывают, они сразу же начинают выполняться и выполняются до тех пор, пока не вернут значение или не бросят исключение. Такое поведение само собой разумеется для любого JS-программиста.

Вызов генератора выглядит так же: quips(«jorendorff») . Но после того, как вы вызовете генератор, он ещё не начнёт выполняться. Вместо этого он вернёт приостановленный объект Generator (в примере выше он под именем iter ). Вы можете считать, что объект Generator — это вызов функции, замороженный во времени. Если точнее, он заморожен прямо в самом начале функции-генератора, перед первой строчкой кода.

Каждый раз, как вы вызываете метод .next() у объекта Generator, вызов функции оттаивает и выполняется, пока не достигнет следующего выражения yield .

Вот почему в примере выше после вызовов iter.next() мы всякий раз получали новое строковое значение. Эти значения производятся выражениями yield в теле quips() .

При последнем вызове iter.next() мы, наконец, достигли конца функции-генератора, так что поле .done результата стало равно true . Добраться до конца функции — это всё равно что вернуть undefined , и именно поэтому поле .value результата равно undefined .

Похоже, сейчас самое время вернуться к странице с говорящей кошкой и как следует поиграться с кодом. Попробуйте добавить yield внутрь цикла. Что произойдёт?

Говоря техническим языком, каждый раз, когда генератор отдаёт значение, его стековый кадр: локальные переменные, аргументы, временные значения и текущая позиция точки выполнения внутри тела генератора — удаляется из стека. Однако, объект Generator хранит ссылку на этот стековый кадр (или его копию), так что последующий вызов .next() возобновит его и продолжит выполнение.

Стоит отметить, что генераторы не являются потоками выполнения. В языках с потоками различные куски кода могут выполняться одновременно, обычно приводя к состояниям гонки, недетерминированности и страстно желанному приросту производительности. Генераторы вообще на это не похожи. Когда генератор выполняется, он работает в том же потоке, что и код его вызвавший. Порядок выполнения последователен и строго определён, и нет никакой параллельности. В отличие от системных потоков, генератор останавливается только на тех местах, где в коде есть yield .

Хорошо. Теперь мы знаем, что такое, генераторы. Мы видели, как генераторы выполняются, приостанавливают и возобновляют свое выполнение. Теперь хороший вопрос: как эти странные возможности могут нам пригодиться?

Генераторы — итераторы

На прошлой неделе мы увидели, что в ES6 итераторы не просто один встроенный класс. Они — точка расширения языка. Вы можете создавать собственные итераторы, и для этого нужно лишь реализовать два метода: [Symbol.iterator]() и .next() .

Но реализация интерфейса — это всегда работа, по меньшей мере, небольшая. Взглянем, как реализация итератора выглядит на практике. В качестве примера возьмём простой итератор range , который всего-навсего считает от одного числа до другого, как в старомодном цикле for (;;) из C.

// Должно "прозвенеть" трижды for (var value of range(0, 3)) < alert("Динь! на этаже № " + value); > 

Вот одно решение, с использованием класса ES6. (Если синтаксис class вам не до конца ясен, не волнуйтесь, мы разберём его в одной из будущих статей.)

class RangeIterator < constructor(start, stop) < this.value = start; this.stop = stop; > [Symbol.iterator]() < return this; > next() < var value = this.value; if (value < this.stop) < this.value++; return done: false, value: value>; > else < return done: true, value: undefined>; > > > // Возвращает новый итератор, который считает от 'start' до 'stop'. function range(start, stop) < return new RangeIterator(start, stop); > 

Так реализация итератора выглядит в Java или Swift. Неплохо. Но вместе с тем и нетривиально. Есть ли ошибки в этом коде? Трудно сказать. Это выглядит совершенно непохоже на изначальный цикл for (;;) , который мы пытаемся эмулировать: протокол итераторов заставляет нас разобрать этот цикл на части.

В этом месте вы можете слегка охладеть к итераторам. Может, ими и здорово пользоваться, но вот реализовывать их трудно.

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

function* range(start, stop) < for (var i = start; i < stop; i++) yield i; > 

Вот этот генератор из 4 строчек полностью заменяет предыдущую 23-строчную реализацию range() , включая весь класс RangeIterator целиком. Это возможно потому что генераторы — это итераторы. У всех генераторов есть встроенная реализация .next() и [Symbol.iterator]() . Всё, что вам нужно — это описать поведение цикла.

Реализация итераторов без генераторов похожа на случай, когда нужно написать длинное электронное письмо используя только пассивный залог. Когда нельзя просто сказать то, что имеется в виду, речь в итоге получается весьма запутанной. RangeIterator длинный и странный потому что он должен описывать функциональность цикла не используя синтаксис циклов. Генераторы — ответ на это.

Для чего ещё можно применить возможность генераторов вести себя как итераторы?

  • Преобразование любого объекта в итерируемый. Просто напишите функцию-генератор, которая перебирает this , отдавая каждое значение по мере работы. Затем установите её объекту как метод [Symbol.iterator] .
  • Упрощение функций, создающих массивы. Предположим, у вас есть функция, которая каждый раз при вызове возвращает массив, вроде такой:

// Делим одномерный массив 'icons' // на массивы длиной 'rowLength'. function splitIntoRows(icons, rowLength) < var rows = []; var nRows = Math.ceil(icons.length / rowLength); for (var i = 0; i < icons.length; i += rowLength) < rows.push(icons.slice(i, i + rowLength)); >return rows; > 

Генераторы могут немного сократить этот код:

function* splitIntoRows(icons, rowLength) < var nRows = Math.ceil(icons.length / rowLength); for (var i = 0; i < icons.length; i += rowLength) < yield icons.slice(i, i + rowLength); > > 
function* filter(test, iterable) < for (var item of iterable) < if (test(item)) yield item; > > 

Итак, генераторы полезны? Разумеется. Это удивительно лёгкий способ реализации собственных итераторов, а итераторы — это новый стандарт для данных и циклов во всём ES6.

Но это ещё не всё, что генераторы могут делать. Может даже выясниться, что это не самое важное из того, что они делают.

Генераторы и асинхронный код

Вот такой код JS я писал раньше:

Может быть, вы встретите что-то похожее в своём коде. Асинхронные APIs обычно требуют колбеков, поэтому приходится писать очередную анонимную функцию всякий раз, когда что-то делаешь. И если у вас есть кусок кода, который делает три вещи, вместо трёх строчек кода вы видите три уровня отступов кода.

Вот ещё кое-что из того JS-кода, что я писал:

>).on('close', function ( ) < done(undefined, undefined); >).on('error', function (error) < done(error); >); 

В асинхронных API используются соглашения об обработке ошибок вместо исключений. У разных API могут быть разные соглашения. В большинстве из них ошибки просто игнорируются по умолчанию. В некоторых из них игнорируется по умолчанию даже обычное успешное выполнение.

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

Генераторы дают нам новую надежду, что это так не останется.

Q.async() — это экспериментальная попытка сделать асинхронный код похожим на синхронный при помощи генераторов и промисов. К примеру:

// Синхронный код, производящий шум function makeNoise( ) < shake(); rattle(); roll(); >// Асинхронный код, производящий шум // Возвращает объект Promise, который разрешится, // когда мы закончим шуметь function makeNoise_async( ) < return Q.async(function* ( ) < yield shake_async(); yield rattle_async(); yield roll_async(); >); > 

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

Если добавить конструкции вроде if или try / catch в версию Q.async , то всё будет работать точно так же, как если бы их добавили в синхронный код. По сравнению с другими способами написания асинхронного кода этот меньше всего ощущается как изучение нового языка.

Если вы дочитали до этого места, возможно, вам понравится очень подробная статья по этой теме от Джеймса Лонга (James Long).

Итак, генераторы освещают наш путь к новой модели асинхронного программирования, которая, кажется, лучше подходит для человеческого мозга. Эта работа ещё не окончена. Среди всего прочего, может помочь синтаксис получше. Предложение асинхронных функций, работающих на промисах и генераторах и вдохновлённых похожими возможностями в C#, уже внесено в таблицу на включение в ES7.

Когда можно воспользоваться этими безумными вещами?

На сервере вы можете применять генераторы уже сегодня в io.js (или в Node с параметром командной строки —harmony ).

Из браузеров пока что генераторы поддерживают только Firefox 27+ и Chrome 39+. Чтобы применять генераторы в вебе, придётся воспользоваться Babel или Traceur и транслировать код ES6 в понятный всем браузерам ES5.

Ещё кое-что, что нельзя не упомянуть: Генераторы впервые были реализованы в JS Бренданом Айком (Brendan Eich), и его подход очень напоминал генераторы в Python, которые в свою очередь были вдохновлены Icon. Они появились в Firefox в далёком 2006. Путь к стандартизации был непростым, синтаксис и поведение за это время немного поменялись. Генераторы ES6 были реализованы как в Firefox, так и в Chrome мастером компиляции Энди Винго (Andy Wingo). Его работа спонсировалась Bloomberg.

yield;

О генераторах ещё можно многое рассказать. Мы не рассмотрели методы .throw() и .return() , необязательные аргументы .next() и синтаксис выражения yield* . Но я считаю, что эта статья уже достаточно длинная, и из неё и так можно узнать много нового. Как и генераторы, мы пока приостановимся и закончим позднее.

Но на следующей неделе давайте немного сменим тему. Мы охватили две сложные темы подряд. Разве не было бы здорово в следующий раз поговорить о функциональности ES6, которая не изменит вашу жизнь? О чем-нибудь простом и очевидно полезном? О чём-то, что вызовет у вас улыбку? В ES6 и такое есть.

В следующей статье: фича, которая прекрасно подойдёт к любому коду, который вы пишете каждый день. Присоединяйтесь на следующей неделе и мы рассмотрим шаблоны строк в деталях.

Руководство по ES6

Узнайте всё о ECMAScript и функциях, добавленных в ES2015 (ES6).

JavaScript · 04.04.2019 · читать 8 мин �� · Автор: Alexey Myzgin

  • Введение
  • Стрелочные функции
  • Новая область this
  • let и const
  • Promises
  • Generators
  • Классы
    • Конструктор
    • super
    • Getters and setters
    • Импорт модулей
    • Экспорт модулей
    • Более простой синтаксис
    • Прототип
    • super()
    • Динамические свойства

    ECMAScript — это стандарт, на котором основан JavaScript, и его часто называют ES.

    ECMAScript 2015, также известный как ES6, является фундаментальной версией стандарта ECMAScript.

    Опубликованный через 4 года после последней версии стандарта ECMAScript 5.1, он также обозначил переход с номера издания на номер года.

    Поэтому его следует называть не ES6 (хотя все называют его таковым), а ES2015.

    ES5 разрабатывался 10 лет, с 1999 по 2009 год, и как таковой он также был фундаментальным и крайне важным пересмотром языка.

    Поскольку этот длинный промежуток времени прошел между ES5.1 и ES6, выпуск полон важных новых функций и существенных изменений в рекомендуемых лучших практиках разработки программ на JavaScript. Чтобы понять, насколько фундаментален ES2015, просто имей в виду, что в этой версии документ с техническими характеристиками увеличился с 250 страниц до 600.

    В этой статье описаны наиболее важные изменения ��.

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

    Визуально, это простое и долгожданное изменение. Например функция:

    const name = function name()  console.log("my name is Alex"); >;
    const name = () =>  console.log("my name is Alex"); >;

    Если тело функции является однострочным, то можно убрать фигурные скобки:

    const name = () => console.log("my name is Alex");

    Также, если у тебя только один параметр, то можно убрать круглые скобки:

    const name = (param) => console.log(`my name is $param>`);

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

    Более подробно о стрелочных функциях здесь.

    Новая область this

    Значение this определяется тем, каким образом вызвана функция. В JavaScript ключевое слово this относится к объекту, к которому оно принадлежит.

    Оно имеет разные значения в зависимости от того, где используется:

    • в методе, this относится к объекту, по отношению к которому вызван метод;
    • в глобальном контексте выполнения this ссылается к глобальному объекту;
    • в функции, значение this зависит от того, каким образом вызвана функция;
    • в обычной функции this относится к глобальному объекту;
    • в стрелочных функциях, this привязан к окружению, в котором была создана функция;
    • в строгом режиме функции, this не определен, если предварительно он не был установлен в контексте исполнения;
    • в событии this относится к элементу, который получил событие;
    • такие методы, как call() и apply() могут ссылать this на любой объект.

    Есть несколько способов объявить переменные в ES6: объявить переменные с помощью var (который мы всегда использовали), плюс сейчас мы можем использовать let и const .

    Эти два способа имеют некоторые атрибуты, которые будут полезны для нас при создании переменных. Но давай сделаем краткий обзор, как отличаются var, let и const.

    Во-первых, переменные var могут быть переопределены или обновлены. Например:

    var width = 100; console.log(width); // 100 width = 200; console.log(width); // 200

    Также они не будут “кричать” о создании одного и того же имени переменной дважды в одной и той же области видимости, так как переменные var могут быть обновлены или переопределены.

    Также важно помнить, как переменные var определены. По сути, scoping или область видимости означает «Где эти переменные доступны для меня?». В случае переменных var они являются областью действия функции, что означает, что они доступны только внутри функции, в которой они созданы. Однако, если они не объявлены в функции, то они имеют глобальную область видимости и доступны во всем окне.

    var — традиционно функциональная область.

    let — это новое объявление переменной, которая имеет ограниченую область видимости.

    Это означает, что объявление переменных let в цикле for , внутри блока if или в обычном блоке, не позволит этой переменной «покинуть» блок, в то время как var поднимется до определения функции.

    const похож на let , но он неизменный, константа (почти!). Если ты попытаешься обновить его то это не сработает, потому что ты не можешь обновить переменную const , так как let .

    Ты можешь решить что const — неизменен. Например, если у тебя есть объект:

    const person =  name: "Alex", age: 28, >;

    и ты попытаешься обновить переменную const , набрав person = < name: 'Alexey' >, то у тебя не получится. Однако свойства переменной const могут измениться. Это потому, что весь объект не является неизменным. Его просто нельзя переназначить полностью. Например:

    const person =  name: "Alex", age: 28, >; person.name = "Alexey";

    Это сработает, но ты не можешь перезаписать всю переменную.

    Promises (далее по тексту — “промисы”) позволяют нам устранить известный «callback hell» или «ад обратных вызовов», хотя они внесли небольшие усложнения (которые были решены в ES2017 с помощью async — конструкции более высокого уровня).

    Промисы использовались разработчиками JavaScript задолго до ES2015, только с помощью множеств различных библиотек, а стандарт нашел общий язык между ними.

    Используя промис, ты можешь переписать этот код

    setTimeout(function ()  console.log("I promised to run after 1s"); setTimeout(function ()  console.log("I promised to run after 2s"); >, 1000); >, 1000);
    const wait = () => new Promise((resolve, reject) =>  setTimeout(resolve, 1000); >); wait() .then(() =>  console.log("I promised to run after 1s"); return wait(); >) .then(() => console.log("I promised to run after 2s"));

    Промис может быть выполнен успешно — resolved или отклонен — rejected . Когда успешно выполняешь промис, запускается then() , а когда отклоняешь запускается catch() . Обычно, внутри твоего промиса, у тебя есть какая-то логика, которая решает, будете ли промис отклонен или выполнен. Он позволяет обрабатывать результаты асинхронных операций так, как если бы они были синхронными. Вместо конечного результата асинхронного метода возвращается промис (обещание) получить результат в какой-то момент в будущем.

    Generators — это особый тип функций, который дает возможность приостановить свое выполнения, а потом позже возобновить. Это позволяет запускать другой код в момент приостановления функции. function со звёздочкой function* определяет функцию-генератор.

    Традиционно JavaScript — единственный основной язык с наследованием на основе прототипов. ES2015 представил классы, которые являются просто синтаксическим сахаром для внутренней работы, что сильно изменил то, как мы строим программы на JavaScript.

    Теперь наследование очень простое и напоминает другие языки объектно-ориентированного программирования:

    class Person  constructor(name)  this.name = name; > hello()  return "Hello, I am " + this.name + "."; > > class Developer extends Person  hello()  return super.hello() + " I am a Frontend Developer."; > > const me = new Developer("Alex"); console.log(me.hello()); // Hello, I am Alex. I am a Frontend Developer.

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

    Классы имеют специальный метод с именем constructor , который вызывается, когда класс инициализируются с помощью new . Родительский constructor наследуется автоматически, если у потомка нет своего метода constructor . Если же потомок имеет свой constructor , то, чтобы унаследовать конструктор родителя нужно использовать super() с аргументами для родителя.

    Слово super используется для вызова функций, принадлежащих родителю объекта.

    Getters and setters

    Геттеры могут быть объявлены как:

    class Person  get fullName()  return `$this.firstName> $this.lastName>`; > >

    get связывает свойство объекта с функцией, которая вызывается при обращении к этому свойству.

    Сеттеры пишутся так же:

    class Person  set age(years)  this.theAge = years; > >

    set связывает свойство объекта с функцией, он будет вызываться при попытке установить это свойство.

    До ES2015 существовало не менее 3-х основных модулей, конкурирующих со стандартами, которые разделили сообщество:

    ES2015 стандартизировал их в общий формат.

    import используется для импорта ссылок на значения, экспортированные из внешнего модуля, и осуществляется через конструкцию import . from . :

    import defaultExport from "module-name"; import * as name from "module-name"; import * from 'module-name' import  namedExport > from 'module-name' import "module-name";

    Экспорт используется для экспорта функций, объектов. Ты можешь писать модули и экспортировать что угодно в другие модули, используя ключевое слово export :

    export  name1, name2,, nameN >; export default выражение; export * from …; export default function () >;

    Шаблонные литералы (Template Literals)

    Шаблонные литералы — это новый синтаксис для создания строк:

    const aString = `A string`;

    Они обеспечивают способ встраивания выражений в строки, эффективно интерполируя значения, используя синтаксис $ :

    const name = "Alex"; const string = `Hey $name>`; // Hey Alex

    Ты также можешь выполнять более сложные выражения:

    const string = `total count is: $1 + 2 + 3>`; const string2 = `result is $foo() ? "x" : "y">`;

    также строки могут занимать несколько строк:

    const string3 = `Hey this string is awesome!`;

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

    Функции теперь поддерживают параметры по умолчанию:

    const sayMyName = function (name = "Alex")  console.log(name); >; sayMyName(); // Alex

    Если при вывозе функции name() мы не передали аргумент, то тогда она возьмет значение по умолчанию «Alex» .

    Оператор распространения spread

    Ты можешь расширить или объединить массив, объект или строку, используя оператор распространения .

    Начнем с массива.

    У нас есть один массив const a = [1, 2, 3] который мы хотим модифицировать, добавив несколько значений:

    const a = [1, 2, 3]; const b = [. a, 4, 5, 6]; // [1, 2, 3, 4, 5, 6]

    Теперь у нас новый массив b который содержит все значения a , плюс свои.

    Также можно создать копию массива:

    const c = [. a];

    spread также работает и для объектов. Клонировать объект можно так:

    const newObj =  . oldObj >;

    Используя строки, оператор распространения spread создает массив с каждым символом в строке:

    const hey = "hey"; const arrayized = [. hey]; // ['h', 'e', 'y']

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

    const f = (one, two) =>  console.log(one, two); // 1, 2 >; const a = [1, 2]; f(. a);

    Деструктуризация — это выражение JavaScript, которое позволяет нам извлекать данные из массивов и объектов.

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

    const person =  firstName: "Alex", dev: true, age: 28, >; const name = person.firstName; const age = person.age; console.log(name, age); // Alex, 28

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

    const  firstName, age > = person;

    Здесь мы создаем переменные с именами firstName , age и берем их значения из объекта person . Иными словами мы берем свойства firstName , age и помещаем их в две новые переменные, которые будут видны родительскому блоку (или window).

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

    const  firstName: name, age > = person;

    Таким образом, мы извлекли значение с firstName или person.firstName и дали ему новое имя name .

    Деструктуризация так же работает на массивах. Например, если тебе нужены только первый и второй элементы:

    const a = [1, 2, 3, 4, 5]; const [first, second] = a; console.log(first, second); // 1, 2

    Еще один вариант как выбрать нужные элементы из массива:

    const [first, second, , , fifth] = a; console.log(first, second, fifth); // 1, 2, 5

    Этот синтаксис создает 3 новые переменные, получая элементы с индексами 0, 1, 4 из массива a :

    Расширенные литералов объекта

    Более простой синтаксис

    Стал более простым синтаксис для переменных, если они имеют одинаковые имена. Вместо этого:

    const name = "Alex"; const x =  name: name, >;

    можно писать так:

    const name = "Alex"; const x =  name, >;

    Прототип может быть указан таким образом:

    const person =  name: "Alex" >; const developer =  __proto__: person, >;
    const person =  name: "Alex", say: () => "Hello " >; const developer =  __proto__: person, say()  return super.say() + this.name; >, >; developer.say(); // Hello Alex

    Имя свойства объекта может быть указано динамически.

    const x =  ["a" + "_" + "b"]: "z", >; x.a_b; // z

    ES5 еще в 2009 году представил цикл forEach() . Единственное, что в этом цикле нет никакого способа сделать разрыв break , как в цикле for .

    ES2015 представил цикл for-of , который сочетает в себе краткость forEach с возможностью break разрыва:

    // перебирает значения массива и выводит в консоль for (const v of ["a", "b", "c"])  console.log(v); > // получает индекс и значение, используя `entries()` for (const [i, v] of ["a", "b", "c"].entries())  console.log(i, v); if (i === 0)  break; > >

    entries() это метод который возвращает iterator позволяя пройтись по всем его ключам/значениям. В нашем случает мы используем деструктуризацию массива где i — это индекс элемента в массиве, а v — это его значение.

    Map и Set (и их соответствующие сборщики мусора WeakMap и WeakSet) являются официальными реализациями двух очень популярных структур данных. Map содержит пары ключ-значение и сохраняет порядок вставки. Любое значение может быть использовано в качестве ключа. Set позволяет сохранять уникальные значения любого типа.

    Новые строковые методы

    Любое строковое значение получило несколько новых методов экземпляра:

    • repeat() повторяет строки указанное количество раз: ‘Ho’.repeat(3) // HoHoHo;
    • codePointAt() возвращает не отрицательное целое число, которое является закодированным в UTF-16 значением кодовой точки.

    Новые методы объекта

    ES6 представил несколько статических методов в пространстве имен объекта:

    • Object.is() метод определяет, являются ли два значения одинаковыми var isSame = Object.is(value1, value2) ;
    • Object.assign() используется для поверхностного копирования всех свойств объекта в целевой объект Object.assign(target, . sources) ;
    • Object.setPrototypeOf устанавливает прототип объекта Object.setPrototypeOf(obj, prototype) .

    ES6 по-человечески

    От переводчика:
    Предлагаю вашему вниманию перевод краткого (действительно краткого) руководства по ES6. В нём можно ознакомиться с основными понятиями стандарта.
    Оригинальный текст в некоторых случаях был дополнен или заменён на более подходящий источник. Например, часть определения ключевого слова const является переводом документации с MDN.
    Чтобы лучше разобраться в некоторых концепциях (для выполнения качественного перевода) использовалось описание стандарта на сайте MDN, руководство «You Don’t Know JS: ES6 & Beyond» и учебник Ильи Кантора.

    1. let, const и блочная область видимости

    Ключевое слово let позволяет объявлять переменные с ограниченной областью видимости — только для блока <. >, в котором происходит объявление. Это называется блочной областью видимости. Вместо ключевого слова var , которое обеспечивает область видимости внутри функции, стандарт ES6 рекомендует использовать let .

    var a = 2; < let a = 3; console.log(a); // 3 >console.log(a); // 2

    Другой формой объявления переменной с блочной областью видимости является ключевое слово const . Оно предназначено для объявления переменных (констант), значения которых доступны только для чтения. Это означает не то, что значение константы неизменно, а то, что идентификатор переменной не может быть переприсвоен.
    Вот простой пример:

    О чём стоит помнить:

    • Когда дело касается поднятия переменных (hoisting) let и const , их поведение отличается от традиционного поведения var и function . И let и const не существуют до своего объявления (от переводчика: для подробностей автор оригинального руководства отсылает к статье Temporal Dead Zone)
    • Областью видимости let и const является ближайший блок.
    • При использовании const рекомендуется использовать ПРОПИСНЫЕ_БУКВЫ.
    • В const одновременно с объявлением переменной должно быть присвоено значение.

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

    Стрелочные функции представляют собой сокращённую запись функций в ES6. Стрелочная функция состоит из списка параметров ( . ) , за которым следует знак => и тело функции.

    // Классическое функциональное выражение let addition = function(a, b) < return a + b; >; // Стрелочная функция let addition = (a, b) => a + b;

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

    А вот пример с использованием блока из фигурных скобок:

    let arr = ['apple', 'banana', 'orange']; let breakfast = arr.map(fruit => < return fruit + 's'; >); console.log(breakfast); // ['apples', 'bananas', 'oranges']

    Это ещё не всё.

    Стрелочные функции не просто делают код короче. Они тесно связаны с ключевым словом this и привязкой контекста.

    Поведение стрелочных функций с ключевым словом this отличается от поведения обычных функций с this . Каждая функция в JavaScript определяет свой собственный контекст this , но внутри стрелочных функций значение this то же самое, что и снаружи (стрелочные функции не имеют своего this ). Посмотрим на следующий код:

    function Person() < // Конструктор Person() определяет `this` как экземпляр самого себя. this.age = 0; setInterval(function growUp() < // Без использования `use strict`, функция growUp() определяет `this` // как глобальный объект, который отличается от `this`, // определённого конструктором Person(). this.age++; >, 1000); > var p = new Person();

    В ECMAScript 3/5 это поведение стало возможным изменить, присвоив значение this другой переменной.

    function Person() < var self = this; self.age = 0; setInterval(function growUp() < // Коллбэк относится к переменной `self`, // значением которой является ожидаемый объект. self.age++; >, 1000); >

    Как сказано выше, внутри стрелочных функций значение this то же самое, что и снаружи, поэтому следующий код работает так, как от него и ожидается:

    function Person() < this.age = 0; setInterval(() =>< this.age++; // `this` относится к объекту person >, 1000); > var p = new Person();

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

    ES6 позволяет установить параметры по умолчанию при объявлении функции. Вот простой пример:

    let getFinalPrice = (price, tax = 0.7) => price + price * tax; getFinalPrice(500); // 850, так как значение tax не задано getFinalPrice(500, 0.2); // 600, значение tax по-умолчанию заменяется на 0.2

    4. Spread / Rest оператор

    . оператор называют как spread или rest, в зависимости от того, как и где он используется.

    При использовании в любом итерируемом объекте (iterable), данный оператор «разбивает» («spread») его на индивидуальные элементы:

    function foo(x, y, z) < console.log(x, y, z); >let arr = [1, 2, 3]; foo(. arr); // 1 2 3

    Другим распространённым использованием оператора . является объединение набора значений в один массив. В данном случае оператор работает как «rest» (от переводчика: не нашёл подходящего перевода на русский язык, из примера ниже всё станет ясно)

    function foo(. args) < console.log(args); >foo(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

    5. Расширение возможностей литералов объекта

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

    function getCar(make, model, value) < return < // с синтаксисом короткой записи можно // пропускать значение свойства, если оно // совпадает с именем переменной, значение // которой мы хотим использовать make, // аналогично make: make model, // аналогично model: model value, // аналогично value: value // вычисляемые свойства теперь работают в // литералах объекта ['make' + make]: true, // Короткая запись метода объекта пропускает // ключевое слово `function` и двоеточие. Вместо // "depreciate: function() <>" можно написать: depreciate() < this.value -= 2500; >>; > let car = getCar('Kia', 'Sorento', 40000); console.log(car); // < // make: 'Kia', // model:'Sorento', // value: 40000, // makeKia: true, // depreciate: function() // >

    6. Восьмеричный и двоичный литералы

    В ES6 появилась новая поддержка для восьмеричных и двоичных литералов.
    Добавление к началу числа 0o или 0O преобразует его в восьмеричную систему счисления (аналогично, 0b или 0B преобразует в двоичную систему счисления). Посмотрим на следующий код:

    let oValue = 0o10; console.log(oValue); // 8 let bValue = 0b10; console.log(bValue); // 2

    7. Деструктуризация массивов и объектов

    Деструктуризация помогает избежать использования вспомогательных переменных при взаимодействии с объектами и массивами.

    function foo() < return [1, 2, 3]; >let arr = foo(); // [1,2,3] let [a, b, c] = foo(); console.log(a, b, c); // 1 2 3 function bar() < return < x: 4, y: 5, z: 6 >; > let < x: a, y: b, z: c >= bar(); console.log(a, b, c); // 4 5 6

    8. Ключевое слово super для объектов

    ES6 позволяет использовать метод super в (безклассовых) объектах с прототипами. Вот простой пример:

    var parent = < foo() < console.log("Привет от Родителя!"); >> var child = < foo() < super.foo(); console.log("Привет от Ребёнка!"); >> Object.setPrototypeOf(child, parent); child.foo(); // Привет от Родителя! // Привет от Ребёнка!

    9. Строковые шаблоны и разделители

    ES6 предоставяляет более простой способ вставки значения переменной или результата выражения (т.н. «интерполяцию»), которые рассчитываются автоматически.

    • $ < . >используется для вычисления значения переменной/выражения.
    • « Обратные кавычки используются как разделитель для таких случаев.
    let user = 'Кевин'; console.log(`Привет, $!`); // Привет, Кевин!

    10. for. of против for. in

    • for. of используется для перебора в цикле итерируемых объектов, например, массивов.
    let nicknames = ['di', 'boo', 'punkeye']; nicknames.size = 3; for (let nickname of nicknames) < console.log(nickname); >// di // boo // punkeye
    • for. in используется для перебора в цикле всех доступных для перебора (enumerable) свойств объекта.
    let nicknames = ['di', 'boo', 'punkeye']; nicknames.size = 3; for (let nickname in nicknames) < console.log(nickname); >// 0 // 1 // 2 // size

    11. Map и WeakMap

    ES6 представляет новые структуры данных — Map и WeakMap . На самом деле, мы используем «Map» в JavaScript всё время. Каждый объект можно представить как частный случай Map .

    Классический объект состоит из ключей (всегда в строковом виде) и значений, тогда как в Map для ключа и значения можно использовать любое значение (и объекты, и примитивы). Посмотрим на этот код:

    var myMap = new Map(); var keyString = "строка", keyObj = <>, keyFunc = function() <>; // устанавливаем значения myMap.set(keyString, "значение, связанное со 'строка'"); myMap.set(keyObj, "значение, связанное с keyObj"); myMap.set(keyFunc, "значение, связанное с keyFunc"); myMap.size; // 3 // получаем значения myMap.get(keyString); // "значение, связанное со 'строка'" myMap.get(keyObj); // "значение, связанное с keyObj" myMap.get(keyFunc); // "значение, связанное с keyFunc"

    WeakMap

    WeakMap это Map , в котором ключи обладают неустойчивыми связями, что позволяет не мешать сборщику мусора удалять элементы WeakMap . Это означает, что можно не беспокоиться об утечках памяти.

    Стоить отметить, что в WeakMap , в отличие от Map , каждый ключ должен быть объектом.

    Для WeakMap есть только четыре метода: delete(ключ) , has(ключ) , get(ключ) и set(ключ, значение) .

    let w = new WeakMap(); w.set('a', 'b'); // Uncaught TypeError: Invalid value used as weak map key var o1 = <>, o2 = function()<>, o3 = window; w.set(o1, 37); w.set(o2, "azerty"); w.set(o3, undefined); w.get(o3); // undefined, потому что это заданное значение w.has(o1); // true w.delete(o1); w.has(o1); // false

    12. Set и WeakSet

    Объекты Set это коллекции уникальных значений. Дублированные значения игнорируются, т.к. коллекция должна содержать только уникальные значения. Значения могут быть примитивами или ссылками на объекты.

    let mySet = new Set([1, 1, 2, 2, 3, 3]); mySet.size; // 3 mySet.has(1); // true mySet.add('строки'); mySet.add(< a: 1, b:2 >);

    Вы можете перебирать Set в цикле с помощью forEach или for. of . Перебор происходит в том же порядке, что и вставка.

    mySet.forEach((item) => < console.log(item); // 1 // 2 // 3 // 'строки' // Object < a: 1, b: 2 >>); for (let value of mySet) < console.log(value); // 1 // 2 // 3 // 'строки' // Object < a: 1, b: 2 >>

    У Set также есть методы delete() и clear() .

    WeakSet

    Аналогично WeakMap , объект WeakSet позволяет хранить объекты с неустойчивыми связями в коллекции. Объект в WeakSet уникален.

    var ws = new WeakSet(); var obj = <>; var foo = <>; ws.add(window); ws.add(obj); ws.has(window); // true ws.has(foo); // false, foo не был добавлен к коллекции ws.delete(window); // удаляет window из коллекции ws.has(window); // false, window был удалён

    13. Классы в ES6

    В ES6 представили новый синтаксис для классов. Здесь стоит отметить, что класс ES6 не представляет собой новую объектно-ориентированную модель наследования. Это просто синтаксический сахар для существующего в JavaScript прототипного наследования.

    Класс в ES6 представляет собой просто новый синтаксис для работы с прототипами и функциями-конструкторами, которые мы привыкли использовать в ES5.

    Функции, записанные с помощью ключевого слова static , используются для объявления статических свойств класса.

    class Task < constructor() < console.log("Создан экземпляр task!"); >showId() < console.log(23); >static loadAll() < console.log("Загружаем все tasks. "); >> console.log(typeof Task); // function let task = new Task(); // "Создан экземпляр task!" task.showId(); // 23 Task.loadAll(); // "Загружаем все tasks. "

    extends и super в классах

    Посмотрим на следующий код:

    class Car < constructor() < console.log("Создаём новый автомобиль"); >> class Porsche extends Car < constructor() < super(); console.log("Создаём Porsche"); >> let c = new Porsche(); // Создаём новый автомобиль // Создаём Porsche

    В ES6 ключевое слово extends позволяет классу-потомку наследовать от родительского класса. Важно отметить, что конструктор класса-потомка должен вызывать super().

    Также, в классе-потомке можно вызвать метод родительского класса с помощью super.имяМетодаРодителя() .

    О чём стоит помнить:

    • Объявления классов не поднимаются наверх (not hoisted). Сначала нужно объявить класс и только после этого использовать его, иначе будет ошибка ReferenceError.
    • Нет необходимости использовать ключевое слово function во время задания функций внутри определения класса.

    14. Тип данных Symbol

    Symbol это уникальный и неизменяемый тип данных, представленный в ES6. Целью Symbol является создание уникального идентификатора, к которому нельзя получить доступ.

    Вот как можно создать Symbol :

    var sym = Symbol("опциональное описание"); console.log(typeof sym); // symbol

    Заметим, что использовать new вместе с Symbol(…) нельзя.

    Если Symbol используется как свойство/ключ объекта, он сохраняется таким специальным образом, что свойство не будет показано при нормальном перечислении свойств объекта.

    var o = < val: 10, [Symbol("случайный")]: "Я - символ", >; console.log(Object.getOwnPropertyNames(o)); // val

    Чтобы извлечь символьные свойства объекта, нужно использовать Object.getOwnPropertySymbols(o)

    15. Итераторы

    Итератор обращается к элементам коллекции по одному, в то же время сохраняя память о своей текущей позиции в этой коллекции. У итератора есть метод next() , который возвращает следующий элемент в последовательности. Этот метод возвращает объект с двумя свойствами: done (окончен ли перебор) и value (значение).

    В ES6 есть метод Symbol.iterator , который определяет итератор для объекта по-умолчанию. При каждой необходимости перебора в цикле для объекта (например, в начале цикла for..of), его метод итератора вызывается без аргументов, и возвращённый итератор используется для того, чтобы получить значения для перебора.

    Посмотрим на массив, который является перебираемым (iterable), и на итератор, который есть у массива для обработки его значений:

    var arr = [11,12,13]; var itr = arr[Symbol.iterator](); itr.next(); // < value: 11, done: false >itr.next(); // < value: 12, done: false >itr.next(); // < value: 13, done: false >itr.next(); //

    Заметим, что можно написать собственный итератор через определение obj[Symbol.iterator]() с описанием объекта.

    Подробнее про итераторы:
    На сайте MDN

    16. Генераторы

    Функции-генераторы представляют собой новую особенность ES6, которая позволяет функции создавать много значений в течение некоторого периода времени, возвращая объект (называемый генератором), который может быть итерирован для выброса значений из функции по одному за раз.

    Функция-генератор возвращает итерируемый объект при своём вызове.
    Функция-генератор записывается с помощью знака * после ключевого слова function , а в теле функции должно присутствовать ключевое слово yield .

    function *infiniteNumbers() < var n = 1; while (true) < yield n++; >> var numbers = infiniteNumbers(); // возвращает перебираемый объект numbers.next(); // < value: 1, done: false >numbers.next(); // < value: 2, done: false >numbers.next(); //

    Каждый раз при вызове yield возвращённое значение становится следующим значением в последовательности.

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

    17. Промисы

    В ES6 появилась встроенная поддержка промисов. Промис это объект, который ждёт выполнения асинхронной операции, после которого (т.е. после выполнения) промис принимает одно из двух состояний: fulfilled (resolved, успешное выполнение) или rejected (выполнено с ошибкой).

    Стандартным способом создания промиса является конструктор new Promise() , который принимает обработчик с двумя функциями как параметрами. Первый обработчик (обычно именуемый resolve ) представляет собой функцию для вызова вместе с будущим значением, когда оно будет готово; второй обработчик (обычно именуемый reject ) является функцией, которая вызывается для отказа от выполнения промиса, если он не может определить будущее значение.

    var p = new Promise(function(resolve, reject) < if (/* условие */) < resolve(/* значение */); // fulfilled successfully (успешный результат) >else < reject(/* reason */); // rejected (ошибка) >>);

    Каждый промис обладает методом then , в котором есть два коллбэка. Первый коллбэк вызывается, если промис успешно выполнен (resolved), тогда как второй коллбэк вызывается, если промис выполнен с ошибкой (rejected).

    p.then((val) => console.log("Промис успешно выполнен", val), (err) => console.log("Промис выполнен с ошибкой", err));

    При возвращении значения от then коллбэки передадут значение следующему коллбэку then .

    var hello = new Promise(function(resolve, reject) < resolve("Привет"); >); hello.then((str) => `$ Мир`) .then((str) => `$!`) .then((str) => console.log(str)) // Привет Мир!

    При возвращении промиса, успешно обработанное значение промиса пройдёт к следующему коллбэку, для того, чтобы эффективно соединить их вместе.
    Эта простая техника помогает избежать ада с коллбэками («callback hell»).

    var p = new Promise(function(resolve, reject) < resolve(1); >); var eventuallyAdd1 = (val) => < return new Promise(function(resolve, reject)< resolve(val + 1); >); > p.then(eventuallyAdd1) .then(eventuallyAdd1) .then((val) => console.log(val)) // 3

    Javascript ES6: A New Frontier for Javascript

    The Javascript programming language has been around for more than 25 years, and in that time it has changed quite a bit, adding new features and new syntax. It used to take years for new features to enter the language, but the modern Javascript standard is updated on a annual basis with new features.

    Many major changes to the Javascript standard came with Javascript ES6. ES6 stands for ECMAScript 6; ECMAScript is another official name for Javascript, since the organization ECMA International creates the standard.

    This article describes Javascript ES6, some of the important new features introduced to the Javascript language, and the legacy of Javascript ES6 since its adoption.

    What is Javascript ES6?

    Javascript ES6 is the version of Javascript released in 2015. Informally, many developers call any version of Javascript released after 2015 “ES6” because of the many new features the 2015 update added to the language. There have been more versions of Javascript released since then — more on that later — but none have gained as much presence as ES6 within the developer community.

    Until 2015, Javascript versions were named by version number. ES6 was the last Javascript version to be named in this way, and before the end of 2015 it was renamed to ES2015 (ECMAScript 2015).

    ES6, then, is actually a legacy name, and the new annual naming convention is why there has been no official “ES7” or higher, even though newer versions of Javascript exist. The two names sometimes cause confusion among developers; it is common to see Javascript ES6 referred to as ES2015.

    As the first version of Javascript in a decade (after ES5), Javascript ES6 added modernized capabilities for the web in the 2010s. The new standard contained a list of features that developers had long wanted along with some optimizations to the language that made creating Javascript programs much easier.

    The most recent version of Javascript is called ES2020, although some people occasionally use the legacy naming system and call it “ES11.” Thanks to common usage of the “ES6” name, though, most information you find about “ES6” is probably applicable to modern Javascript in general. Check the Javascript standard if you’re unsure.

    What are Some New Features of Javascript ES6?

    Javascript ES6 was a product of modern web development needs and years of input from the Javascript community. Many new features in Javascript ES6 addressed capabilities the Javascript language lacked or improved upon the language’s shortcomings.

    Declaring Variables in New Ways

    Before Javascript ES6, Javascript variables were always defined using the var keyword. The var keyword could be used in any scope, and there were no restrictions to using the keyword in any particular context. Although variables defined with var were flexible, they could be redefined elsewhere in a program, leading to potential confusion or unexpected behavior.

    var myVar = "blah"; // myVar = "blah" var myVar = "blah2"; // myVar = "blah2" -- same variable!

    Defining variables with var is still allowed in Javascript ES6 and beyond, but it is strongly discouraged. (This is also why Udacity’s Javascript examples never use var unless it is essential to illustrate a concept.) The Javascript interpreter will not prevent you from using var , but there are better options available, const and let .

    Variables declared with let may be updated, but not redefined.

    let myVar2 = "abc"; // Declare myVar2 with value "abc" myVar2 = "def"; // Update myVar2 to value "def" let myVar2 = "xyz"; // Will throw an error at redefining attempt!

    Variables declared with const cannot be updated or redefined.

    const myVar3 = 123; // Declared and defined as a constant myVar3 = 45; // Will throw an error at updating attempt! let myVar3 = 678; // Will throw an error at redefining attempt!

    Writing Functions More Simply

    Writing out a function body and function call can be cumbersome when a function is relatively simple, as in this example:

    function myName() < return "Jane Doe"; >let me = myName();

    Arrow functions make creating these types of functions simpler by removing the need for explicit function structuring. The arrow operator (=>) handles that instead:

    let me = () =>

    If a function only has one statement inside, you don’t even need braces or a return statement for this to work.

    let me = () => "Jane Doe";

    You can pass and use any number of parameters with arrow functions just like with longer function declarations. As long as the body of the function contains one statement only, you can use the shortest arrow function form.

    let me = (name, age) => "My name is " + name + " and I am " + age;

    Creating True Javascript Classes

    A complaint among Javascript developers for many years was that Javascript, unlike many modern scripting languages, didn’t allow developers to create encapsulated classes. Javascript ES6 gave developers that ability.

    // "Class" before ES6 function MyClass(param1, param2) < this.p1 = param1; this.p2 = param2; >// Class in ES6 and later class MyClass < constructor(param1, param2) < this.p1 = param1; this.p2 = param2; >>

    Javascript ES6 classes have explicit constructors, may have class properties, and may have static methods, which are associated with the class rather than an instance of that class. Although the syntax for simple constructors is not dramatically different in ES6, the additional capabilities of ES6 classes are a major functional addition.

    This example demonstrates a simple Javascript ES6 class containing an explicit constructor, class properties, and a static method.

    class MyExample < // Class declaration constructor(param1, param2) < // Explicit constructor this.p1 = param1; // Class property this.p2 = param2; // Class property >static greet(name) < // Static method return "Hello " + name; >>

    What is the Legacy of Javascript ES6?

    There was a long delay between Javascript ES5 and ES6, which was unusual over Javascript’s history. The first four versions of Javascript, developed in the 1990s, were released on an annual basis, but the Javascript community got used to long delays between versions after that.

    Javascript ES6 re-established the annual release schedule of new Javascript versions, and as of 2020, a new version of Javascript has been released regularly each year since 2015. Changes to the Javascript language now happen much more quickly as a result; for example, over five years, two new data types were added.

    Annual Javascript releases have also revitalized the Javascript community; the popularity of Javascript went through a slump in the 2000s but has since become immensely popular again thanks to Javascript ES6. The large community actively debates new additions to the language, and there are multiple ways for even novice developers to submit their ideas for Javascript’s future. None of this would have been possible without Javascript ES6 setting a precedent.

    Conclusion

    Javascript ES6 transformed not only the way developers use Javascript, but the update processes of the Javascript language as well. Thanks to Javascript ES6, new features enter the Javascript language faster than ever before and Javascript can keep up with the changing modern web.

    Enroll in our Intro to Programming Nanodegree Program today to learn more about Javascript ES6 and other programming concepts.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *