ViZhe / ES6-for-humans

A kickstarter guide to writing ES6.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ES6 для людей


Содержание


Переводы


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

MDN: let, const
javascript.ru: 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]
}

Важно помнить:

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

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

javascript.ru: Стрелочные функции

Выражения стрелочных функций имеют более короткий синтаксис по сравнению с функциональными выражениями и лексически привязаны к значению this (но не привязаны к собственному this, arguments, super, new.target). Стрелочные функции всегда анонимные.

let addition = function(a, b) {
    return a + b
}

// Реализация со стрелочной функцией
let addition = (a, b) => a + b // Краткая форма.

let additions = (a, b) => { // Блочная форма.
  return a + b
}

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

Не имеют своего this

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

function Person() {
  // В конструктор Person() `this` указывает на себя.
  this.age = 0

  setInterval(function growUp() {
    // В нестрогом режиме, в функции 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)


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

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

ES6 позволет задавать формальным параметрам функции значения по умолчанию, если для них не указано значение или передан undefined.

let getFinalPrice = (price, tax=6) => (price + price) * tax
console.log(getFinalPrice(50))            // 600
console.log(getFinalPrice(50, 2))         // 200
console.log(getFinalPrice(50, null))      // 0
console.log(getFinalPrice(50, 'string'))  // null
console.log(getFinalPrice(50, undefined)) // 600

4. Оператор расширения / Оставшиеся параметры

MDN: Spread operator, Rest parameters
javascript.ru: Spread и Rest

Оператор расширения позволяет расширять выражения в тех местах, где предусмотрено использование нескольких аргументов (при вызовах функции) или ожидается несколько элементов (для массивов).

function foo(x, y, z) {
  console.log(x, y, z)
}

let arr = [1, 2, 3]
foo(...arr) // 1 2 3

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

const foo = (...args) => {
  console.log(args)
}
foo(1, 2, 3, 4, 5) // [1, 2, 3, 4, 5]

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

javascript.ru: Короткое свойство, Вычисляемые свойства

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

const getCar = (make, model, value) => {
  return {
    // значение свойства можно опустить,
    // если оно соответствует названию переменной
    make,  // эквивалентно: make: make
    model, // эквивалентно: model: model
    value, // эквивалентно: value: value

    // вычисляемые значения теперь работают
    // с литералами объекта
    ['make' + make]: true,

    // при определении метода сокращенным синтаксисом,
    // опускается ключевое слово `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. Восьмеричные и Бинарные литералы

MDN: Numeric literals

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

let octalValue = 0o10 // `0o` или `0O` - восьмеричный
console.log(octalValue) // 8

let binaryValue = 0b10 // `0b` или `0B` - бинарный
console.log(binaryValue) // 2

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

MDN: Destructuring assignment
javascript.ru: Деструктуризация

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

const arr = [1, 2, 3]
let [a, b, c] = arr
console.log(a, b, c) // 1 2 3

const obj = {
  x: 4,
  y: 5,
  z: 6
}
let {x: aX, y: bY, z: cZ} = obj
console.log(aX, bY, cZ) // 4 5 6

8. super в объектах

MDN: Super

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

const parent = {
  foo() {
    console.log('Hello from the Parent')
  }
}

const child = {
  foo() {
    super.foo()
    console.log('Hello from the Child')
  }
}

Object.setPrototypeOf(child, parent)
child.foo() // Hello from the Parent
            // Hello from the Child

9. Шаблонные строки

MDN: Template strings

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

let user = 'Kevin'
console.log(`Hi ${user}!`) // Hi Kevin!

console.log(`5 + 5 = ${5 + 5}!`) // 5 + 5 = 10!

console.log(`string text line 1
string text line 2`)
// string text line 1
// string text line 2

10. Отличия for...of и for...in

MDN: 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 обходит имена свойств.

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 = 'a string',
    keyObj = {},
    keyFunc = function () {}

// setting the values
myMap.set(keyString, 'value associated with "a string"')
myMap.set(keyObj, 'value associated with keyObj')
myMap.set(keyFunc, 'value associated with keyFunc')

myMap.size; // 3

// getting the values
myMap.get(keyString)    // "value associated with 'a string'"
myMap.get(keyObj)       // "value associated with keyObj"
myMap.get(keyFunc)      // "value associated with keyFunc"

WeakMap

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

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

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

let w = new WeakMap()
w.set('a', 'b') // Uncaught TypeError: Invalid value used as weak map key

let o1 = {}
let o2 = function(){}
let o3 = window

w.set(o1, 37)
w.set(o2, 'azerty')
w.set(o3, undefined)

console.log(w.get(o3))  // undefined, потому что мы установили это значение

console.log(w.has(o1)) // true
w.delete(o1)
console.log(w.has(o1)) // false

12. Set и WeakSet

MDN: Set
javascript.ru: Set

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

let mySet = new Set([1, 1, 2, 2, 3, 3])
mySet.size // 3
mySet.has(1) // true
mySet.add('strings')
mySet.add({ a: 1, b:2 })

Set обладает и другими методами.

forEach и for...of позволяют пройти значения Set в порядке их добавления в набор.

mySet.forEach((item) => {
  console.log(item)
  // 1
  // 2
  // 3
  // 'strings'
  // Object { a: 1, b: 2 }
})

for (let value of mySet) {
  console.log(value)
  // 1
  // 2
  // 3
  // 'strings'
  // Object { a: 1, b: 2 }
}

WeakSet

MDN: WeakSet
javascript.ru: Weakmap и Weakset

Объект WeakSet - коллекция, элементами которой могут быть только объекты. Ссылки на эти объекты в WeakSet являются слабыми. Каждый объект может быть добавлен в WeakSet только один раз.

var ws = new WeakSet()
var obj = {}
var foo = {}

ws.add(window)
ws.add(obj)

console.log(ws.has(window)) // true
console.log(ws.has(foo)) // false, т.к. мы не добавили `foo` в набор

ws.delete(window) // удаляет `window` из набора
console.log(ws.has(window))    // false, т.к. мы удалили `window` из наборы

13. Классы в ES6

MDN: Classes

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

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

class Task {
  constructor() {
    console.log('task instantiated!')
  }

  showId() {
    console.log(23)
  }

  static loadAll() {
    console.log('Loading all tasks..')
  }
}

console.log(typeof Task) // function
let task = new Task() // "task instantiated!"
task.showId() // 23
Task.loadAll() // "Loading all tasks.."
task.loadAll() // error: task.loadAll is not a function

extends and super в классах

Ключевое слово extends используется в объявлениях классов и выражениях классов для создания класса дочернего относительно другого класса.

Ключевое слово super используется для вызова функций на родителе объекта.

class Car {
  constructor() {
    console.log('Creating a new car')
  }
}

class Porsche extends Car {
  constructor() {
    super()
    console.log('Creating Porsche')
  }
}

let c = new Porsche()
// Creating a new car
// Creating Porsche

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

Использовать метод родительского класса в методах дочернего класса можно с помощью super.parentMethodName().

Важно:

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

14. Символ

MDN: Symbol

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

Создадим символ:

const sym = Symbol('some optional description');
console.log(typeof sym) // symbol

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

const sym = new Symbol() // TypeError

Символы невидимы при итерации for...in. В дополнение к этому, Object.getOwnPropertyNames() не вернет символьные свойства объекта.

const obj = {
  val: 10,
  [ Symbol('random') ]: 'I\'m a symbol'
}

console.log(Object.getOwnPropertyNames(obj)) // ["val"]
for (let i in obj) {
  console.log(i) // val
}

Символьное свойство объекта можно получить с помощью Object.getOwnPropertySymbols(obj).


15. Итераторы

MDN: Iterators

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

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

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

let arr = [11, 12, 13]
let itr = arr[Symbol.iterator]()

console.log(itr.next()) // { value: 11, done: false }
console.log(itr.next()) // { value: 12, done: false }
console.log(itr.next()) // { value: 13, done: false }

console.log(itr.next()) // { value: undefined, done: true }

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


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

MDN: Generators

Генераторы — это специальный тип функции, который работает как фабрика итераторов. Функция становится генератором, если содержит один или более yield операторов и использует function* синтаксис.

Генераторы позволяют определить алгоритм перебора, написав единственную функцию, которая умеет поддерживать собственное состояние.

function* infiniteNumbers() {
  let n = 1
  while (true) {
    yield n++
  }
}

let numbers = infiniteNumbers() // возвращает итерируемый объект

console.log(numbers.next()) // { value: 1, done: false }
console.log(numbers.next()) // { value: 2, done: false }
console.log(numbers.next()) // { value: 3, done: false }

После каждого вызова генератор получает следующее в последовательности значение.

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


17. Promise

MDN: Promise

Интерфейс Promise (обещание) представляет собой обертку для значения, неизвестного на момент создания обещания. Он позволяет обрабатывать результаты асинхронных операций так, как если бы они были синхронными: вместо конечного результата асинхронного метода возвращается обещание получить результат в некоторый момент в будущем.

При создании Обещание находится в ожидании (pending), а затем может стать выполнено (fulfilled), вернув полученный результат (значение), или отклонено (rejected), вернув причину отказа. В любом из этих случаев вызывается обработчик, прикрепленный к обещанию методом then. Если в момент прикрепления обработчика обещание уже сдержано или нарушено, он все равно будет выполнен, т.е. между выполнением обещания и прикреплением обработчика нет «состояния гонки», как, например, в случае с событиями в DOM.

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

var p = new Promise((resolve, reject) => {
  if (/* условие */) {
    resolve(/* значение */)  // операция завершена успешно
  } else {
    reject(/* причина */)  // операция завершена с ошибкой.
  }
})

У обещания есть метод then, который принимает такие же аргументы, как и само обещание.

p.then(
  val => console.log('Promise Resolved', val),
  err => console.log('Promise Rejected', err)
)

Возвращаемое значение из then передается в следующий then через аргумент.

var hello = new Promise((resolve, reject) => {
  resolve('Hello')
})

hello.then(str => `${str} World`)
  .then(str => `${str}!`)
  .then(str => console.log(str)) // Hello World!

Очередь асинхронных событий

Для последовательного выполнения асинхронных действий можно связять вызовы then.

Когда ты возвращаешь что-то из колбэка then, происходит немного магии. Если ты возвращаешь любое значение, это значение передастся функции обратного вызова следующего then. А если ты вернёшь что-то похожее на обещание, следующий then подождёт его и вызовет колбэк только когда оно выполнится. Это простой способ избежать "ад колбеков (callback hell)".

const p = new Promise((resolve, reject) => {
  resolve(1)
})

const increment = val => {
  return new Promise((resolve, reject) => {
    resolve(val + 1)
  })
}

p.then(increment)
  .then(val => {
    console.log(val) // 2
    return val
  })
  .then(val => val + 1)
  .then(val => {
    console.log(val) // 3
    return val
  })
  .then(increment)
  .then(val => console.log(val)) // 4

About

A kickstarter guide to writing ES6.