Планета гаджетов / технологий
Содержание
Продолжение шпаргалки для повседневного использования по ES2015 [ES6] с примерами. Делитесь своими советами в комментариях!
Мэпы — это очень полезная структура данных. До ES6 хеш-мэпы создавались через объекты:
Однако это не защищает от случайной перегрузки функций с конкретными именами свойств:
> getOwnProperty({ hasOwnProperty: ‘Hah, overwritten’}, ‘Pwned’); > TypeError: Property ‘hasOwnProperty’ is not a function |
Настоящие мэпы позволяют устанавливать значения (set
), брать их (get
), искать их (search
) и многое другое.
> map.set(‘name’, ‘david’); > map.get(‘name’); // david > map.has(‘name’); // true |
Самое замечательное — это то, что теперь мы не обязаны использовать только строки. Можно использовать любой тип как ключ, и он не будет приведён к строковому виду.
[‘name’, ‘david’], [function () {}, ‘function’] for (let key of map.keys()) { console.log(typeof key); // > string, boolean, number, object, function |
Примечание: использование сложных величин (функций, объектов) невозможно при проверке на равенство при использовании методов наподобие
map.get()
. Поэтому используйте простые величины: строки, логические переменные и числа.
Также по мэпам можно итерироваться через .entries()
:
for (let [key, value] of map.entries()) { console.log(key, value); |
В версиях младше ES6 было несколько способов хранения приватных данных. Например, можно было использовать соглашения по именованию:
constructor(age) { this._age = age; _incrementAge() { this._age += 1; |
Но такие соглашения могут запутать, да и не всегда их придерживаются. Вместо этого можно использовать WeakMaps:
let _age = new WeakMap(); constructor(age) { _age.set(this, age); let age = _age.get(this) + 1; _age.set(this, age); if (age > 50) { console.log(‘Midlife crisis’); |
Фишкой WeakMaps является то, что ключи приватных данных не выдают имена свойств, которые можно увидеть, используя Reflect.ownKeys()
:
> const person = new Person(50); > person.incrementAge(); // ‘Midlife crisis’ > Reflect.ownKeys(person); // [] |
Практическим примером использования WeakMaps является хранение данных, связанных с элементом DOM, при этом сама DOM не захламляется:
let el = document.getElementById(‘someElement’); // Store a weak reference to the element with a key map.set(el, ‘reference’); // Access the value of the element let value = map.get(el); // ‘reference’ el.parentNode.removeChild(el); // map is empty, since the element is destroyed |
Как видно выше, когда объект уничтожается сборщиком мусора, WeakMap автоматически удаляет пару ключ-значение, которая идентифицировалась этим объектом.
Обещания, о которых мы подробно рассказывали в отдельной статье, позволяют превратить «горизонтальный» код:
func1(function (value1) { func2(value1, function (value2) { func3(value2, function (value3) { func4(value3, function (value4) { func5(value4, function (value5) { // Do something with value 5 }); }); |
В вертикальный:
.then(func5, value5 => { // Do something with value 5 |
До ES6, приходилось использовать bluebird или Q. Теперь Promises реализованы нативно:
new Promise((resolve, reject) => reject(new Error(‘Failed to fulfill Promise’))) .catch(reason => console.log(reason)); |
У нас есть два обработчика, resolve (функция, вызываемая при выполнении обещания) и reject (функция, вызываемая при невыполнении обещания).
Преимущества Promises: обработка ошибок с кучей вложенных коллбэков — это ад. Обещания же выглядят гораздо приятнее. Кроме того, значение обещания после его разрешения неизменно.
Вот практический пример использования Promises:
var request = require(‘request’); return new Promise((resolve, reject) => { request.get(url, (error, response, body) => { resolve(JSON.parse(body)); resolve({}); |
Мы также можем распараллеливать обещания для обработки массива асинхронных операций, используя Promise.all()
:
‘/api/issues/opened’, ‘/api/issues/assigned’, ‘/api/issues/completed’, ‘/api/issues/comments’, let promises = urls.map((url) => { return new Promise((resolve, reject) => { $.ajax({ url: url }) .done((data) => { resolve(data); // Do something with results of all our promises |
Подобно обещаниям, позволяющим нам избежать ада коллбэков, генераторы позволяют сгладить код, придавая асинхронному коду синхронный облик. Генераторы — это функции, которые могут приостановить своё выполнение и впоследствии вернуть значение выражения.
Простой пример использования приведён ниже:
function* sillyGenerator() { var generator = sillyGenerator(); > console.log(generator.next()); // { value: 1, done: false } > console.log(generator.next()); // { value: 2, done: false } > console.log(generator.next()); // { value: 3, done: false } > console.log(generator.next()); // { value: 4, done: false } |
next позволяет передать генератор дальше и вычислить новое выражение. Пример выше предельно прост, но на самом деле генераторы можно использовать для написания асинхронного кода в синхронном виде:
// Hiding asynchronousity with Generators getJSON(url, function(response) { generator.next(response); |
А вот функция-генератор, которая возвращает наши данные:
var entry1 = yield request(‘http://some_api/item1’); var data1 = JSON.parse(entry1); var entry2 = yield request(‘http://some_api/item2’); var data2 = JSON.parse(entry2); |
Благодаря силе yield
мы можем быть уверены, что в entry1
будут нужные данные, которые будут переданы в data1
.
Тем не менее, для обработки ошибок придётся что-то придумать. Можно использовать Promises:
return new Promise((resolve, reject) => { getJSON(url, resolve); |
И мы пишем функцию, которая будет проходить по генератору, используя next
, который в свою очередь будет использовать метод request
:
function iterateGenerator(gen) { var generator = gen(); (function iterate(val) { var ret = generator.next(); if(!ret.done) { ret.value.then(iterate); |
Дополняя обещанием наш генератор, мы получаем понятный способ передачи ошибок путём .catch
и reject
. При этом использовать генератор всё так же просто:
iterateGenerator(function* getData() { var entry1 = yield request(‘http://some_api/item1’); var data1 = JSON.parse(entry1); var entry2 = yield request(‘http://some_api/item2’); var data2 = JSON.parse(entry2); |
Хотя эта функция появится только в ES2016, async await
позволяет нам делать то же самое, что и в предыдущем примере, но с меньшими усилиями:
var request = require(‘request’); return new Promise(function(resolve, reject) { request(url, function(error, response, body) { resolve(body); var data = await getJSON(); console.log(data); // NOT undefined! |
По сути, это работает так же, как и генераторы, но использовать лучше именно эту функцию.
ES6 привнесла поддержку геттеров и сеттеров. Вот пример:
constructor(name) { this._name = name; if(this._name) { return ‘Mr. ‘ + this._name.toUpperCase(); return undefined; set name(newName) { if (newName == this._name) { console.log(‘I already have this name.’); } else if (newName) { this._name = newName; return false; var emp = new Employee(«James Bond»); // uses the get method in the background console.log(emp.name); // Mr. JAMES BOND // uses the setter in the background console.log(emp.name); // Mr. BOND 007 |
Свежие браузеры также позволяют использовать геттеры / сеттеры в объектах, и тогда их можно использовать для вычисленных свойств, добавляя слушатели перед ними:
console.log(‘Getting FullName’); return this.firstName + ‘ ‘ + this.lastName; set fullName (name) { console.log(‘Setting FullName’); var words = name.toString().split(‘ ‘); this.firstName = words[0] || »; this.lastName = words[1] || »; person.fullName; // James Bond person.fullName = ‘Bond 007’; person.fullName; // Bond 007 |
Символы существовали и до ES6, но теперь стал доступен публичный интерфейс для их прямого использования. Символы неизменяемы и уникальны, и их можно использовать в качестве ключей любого хеша.
Вызов Symbol()
или Symbol(description)
создаст уникальный символ, недоступный глобально. Symbol()
обычно используется для добавления своей логики в сторонние объекты или пространства имён, но нужно быть осторожным с дальнейшими обновлениями этих библиотек. Например, если вы хотите добавить метод refreshComponent
в класс React.Component
, убедитесь, что он не совпадёт с методом, добавленном в следующем обновлении:
const refreshComponent = Symbol(); React.Component.prototype[refreshComponent] = () => { |
Symbol.for(key)
создаст символ, который по-прежнему будет неизменяемым и уникальным, но доступным глобально. Два идентичных вызова Symbol.for(key)
вернут одну и ту же сущность Symbol.
Примечание: это неверно для
Symbol(description)
:
Symbol(‘foo’) === Symbol(‘foo’) // false Symbol.for(‘foo’) === Symbol(‘foo’) // false Symbol.for(‘foo’) === Symbol.for(‘foo’) // true |
Символы, в частности, Symbol.for(key)
, обычно используют для интероперабельности, производя поиск символьного поля в аргументах стороннего объекта с известным интерфейсом, например:
const specialRead = Symbol.for(‘specialRead’); if (obj[specialRead]) { const reader = obj[specialRead](); // do something with reader throw new TypeError(‘object cannot be read’); |
А в другой библиотеке:
const specialRead = Symbol.for(‘specialRead’); [specialRead]() { const reader = createSomeReaderFrom(this); return reader; |
В качестве примера использования символов для интероперабельности стоит отметить
Symbol.iterator
, существующий во всех итерируемых типах ES6: массивах, строках, генераторах и т.д. При запуске метода возвращается объект с интерфейсом итератора.
Кстати, познакомиться с примерами использования всех новых возможностей можно, пройдя обучающий видеокурс по JavaScript, о котором мы писали ранее.
По материалам es6-cheatsheetИван Бирюков, страж правописания