Awesome
ES6 по-человечески
<br> *От переводчика:* <br> *Представляю вам перевод очень краткого руководства по стандарту ES6.* *Оригинальный текст в некоторых случаях был дополнен или заменён на более подходящий источник. Например, часть определения ключевого слова `const` является переводом документации с [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const).*Чтобы лучше разобраться в некоторых концепциях (для выполнения качественного перевода) использовалось описание стандарта на сайте MDN, руководство "You Don't Know JS: ES6 & Beyond" и учебник Ильи Кантора.
<br>Содержание
let
,const
и блочная область видимости- Стрелочные функции
- Параметры по умолчанию
- Spread/Rest оператор
- Расширение возможностей литералов объекта
- Восьмеричный и двоичный литералы
- Деструктуризация массивов и объектов
- Ключевое слово super для объектов
- Строковые шаблоны и разделители
- for...of против for...in
- Map и WeakMap
- Set и WeakSet
- Классы в ES6
- Тип данных Symbol
- Итераторы
- Генераторы
- Промисы
На других языках
- Оригинал на английском (Спасибо metagrover)
- Китайский перевод (Спасибо barretlee)
- Португальский перевод (Спасибо alexmoreno)
1. let, const и блочная область видимости
Ключевое слово let
позволяет объявлять переменные с ограниченной областью видимости - только для блока {...}, в котором происходит объявление. Это называется блочной областью видимости. Вместо ключевого слова var
, которое обеспечивает область видимости внутри функции, стандарт ES6 рекомендует использовать let
.
var a = 2;
{
let a = 3;
console.log(a); // 3
}
console.log(a); // 2
Другой формой объявления переменной с блочной областью видимости является ключевое слово const
. Оно предназначено для объявления переменных (констант), значения которых доступны только для чтения. Это означает не то, что значение константы неизменно, а то, что идентификатор переменной не может быть переприсвоен.
Вот простой пример:
{
const ARR = [5, 6];
ARR.push(7);
console.log(ARR); // [5,6,7]
ARR = 10; // TypeError
ARR[0] = 3; // значение можно менять
console.log(ARR); // [3,6,7]
}
О чём стоит помнить:
-
Когда дело касается поднятия переменных (hoisting)
let
иconst
, их поведение отличается от традиционного поведенияvar
иfunction
. Иlet
иconst
не существуют до своего объявления (от переводчика: для подробностей автор оригинального руководства отсылает к статье Temporal Dead Zone) -
Областью видимости
let
иconst
является ближайший блок. -
При использовании
const
рекомендуется использовать ПРОПИСНЫЕ_БУКВЫ. -
В
const
одновременно с объявлением переменной должно быть присвоено значение. -
let
(как иconst
) объявленные в циклеfor
(иfor (in)
) так же попадает в блочную область видимости этого цикла:for (let i=0;i<10;i++) {/* ... */}; console.log(i); // → RefferenceError: i is not defined
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();
Узнать больше о 'Лексическом this' в стрелочных функциях на сайте MDN
<br>3. Параметры по умолчанию
ES6 позволяет установить параметры по умолчанию при объявлении функции. Вот простой пример:
let getFinalPrice = (price, tax = 0.7) => price + price * tax;
getFinalPrice(500); // 850, так как значение tax не задано
getFinalPrice(500, 0.2); // 600, значение tax по-умолчанию заменяется на 0.2
<br>
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
Spread также отлично походит для формирования нового объекта из другого объекта(ов):
const defaults = {avatar: 'placeholder.jpg', active: false}
const userData = {username: 'foo', avatar: 'bar.jpg'}
console.log({created: '2017-12-31', ...defaults, ...userData})
// {created: "2017-12-31", avatar: "bar.jpg", active: false, username: "foo"}
Обратите внимание на ключ avatar
. Он присутствует в обоих исходных объектах. При формировании нового объекта значение ключа из первого объекта перезатёрлось значением из второго объекта.
Новые массивы также могут быть выразительно сформированы:
const arr1 = [1, 2, 3];
const arr2 = [7, 8, 9];
console.log([...arr1, 4, 5, 6, ...arr2]) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
Другим распространённым использованием оператора ...
является объединение набора значений в один массив. В данном случае оператор работает как "rest" (от переводчика: не нашёл подходящего перевода на русский язык, из примера ниже всё станет ясно)
function foo(...args) {
console.log(args);
}
foo(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]
<br>
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()
// }
<br>
6. Восьмеричный и двоичный литералы
В ES6 появилась новая поддержка для восьмеричных и двоичных литералов.
Добавление к началу числа 0o
или 0O
преобразует его в восьмеричную систему счисления (аналогично, 0b
или 0B
преобразует в двоичную систему счисления). Посмотрим на следующий код:
let oValue = 0o10;
console.log(oValue); // 8
let bValue = 0b10;
console.log(bValue); // 2
<br>
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
<br>
8. Ключевое слово super для объектов
ES6 позволяет использовать метод super
в (безклассовых) объектах с прототипами. Вот простой пример:
var parent = {
foo() {
console.log("Привет от Родителя!");
}
}
var child = {
foo() {
super.foo();
console.log("Привет от Ребёнка!");
}
}
Object.setPrototypeOf(child, parent);
child.foo(); // Привет от Родителя!
// Привет от Ребёнка!
<br>
9. Строковые шаблоны и разделители
ES6 предоставяляет более простой способ вставки значения переменной или результата выражения (т.н. "интерполяцию"), которые рассчитываются автоматически.
- <code>`${ ... }`</code> используется для вычисления значения переменной/выражения.
- <code>`</code> Обратные кавычки используются как разделитель для таких случаев.
let user = 'Кевин';
console.log(`Привет, ${user}!`); // Привет, Кевин!
<br>
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
<br>
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
<br>
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 был удалён
<br>
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.имяМетодаРодителя()
.
Узнать больше о классах на сайте MDN
О чём стоит помнить:
- Объявления классов не поднимаются наверх (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(); // { value: undefined, done: true }
Заметим, что можно написать собственный итератор через определение obj[Symbol.iterator]()
с описанием объекта.
Подробнее про итераторы: На сайте MDN
<br>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(); // { value: 3, done: false }
Каждый раз при вызове yield
возвращённое значение становится следующим значением в последовательности.
Также заметим, что генераторы вычисляют свои возвращённые значения по запросу, что позволяет им эффективно представлять последовательности, затратные с точки зрения вычислений, или даже бесконечные последовательности.
<br>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) => `${str} Мир`)
.then((str) => `${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