- let, const и область видимости
- Стрелочные функции
- Параметры функции по умолчанию
- Оператор расширения / Оставшиеся параметры
- Расширение литералаов объекта
- Восьмеричные и Бинарные литералы
- Деструктуризация массивов и объектов
- super в объектах
- Шаблонные строки
- Отличия for...of и for...in
- Map и WeakMap
- Set и WeakSet
- Классы в ES6
- Символ
- Итераторы
- Генераторы
- Promise (Обещание)
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
) должна быть определена при объявлении.
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)
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
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]
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()
// }
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
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
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
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
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
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
Объекты 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` из наборы
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
.
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)
.
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]()
с описанием объекта.
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 }
После каждого вызова генератор получает следующее в последовательности значение.
Обратите внимание, что генераторы вычисляют значение по требованию, это позволяет им эффективно вычислять даже бесконечные последовательности.
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