
Дізнайтесь більше про нові кар'єрні можливості в EchoUA. Цікаві проекти, ринкова оплата, гарний колектив. Надсилайте резюме та приєднуйтеся до нас.
Продовження шпаргалки для повсякденного використання по ES2015 [ES6] з прикладами. Діліться своїми порадами в коментарях!
Мепи
Мепи – це дуже корисна структура даних. До ES6 хеш-мепи створювалися через об’єкти:
var map = new Object ();map[key1] = 'value1';map[key2] = 'value2';
Проте це не захищає від випадкового перевантаження функцій з конкретними іменами властивостей:
> getOwnProperty ({ hasOwnProperty: 'Hah, overwritten'}, 'Pwned');
> TypeError: Property 'hasOwnProperty' is not a function
Справжні мепи дозволяють установлювати значення (set
), брати їх (get
), шукати (search
) і багато чого іншого.
let map = new Map ();> map.set ('name', 'david');> map.get ('name'); // david> map.has ('name'); // true
Найчудовіше – це те, що тепер ми не зобов’язані використовувати тільки рядки. Можна використати будь-який тип як ключ, і він не буде приведений до рядкового виду.
let map = new Map ([ ['name', 'david'], [true, 'false'], [1, 'one'], [{}, 'object'], [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 було декілька способів зберігання приватних даних. Наприклад, можна було використати угоди з іменування:
class Person { constructor (age) { this._ age = age; }_incrementAge (){ this._ age += 1; }}
Проте такі угоди можуть заплутати, та й не завжди їх дотримуються. Замість цього можна використати WeakMaps:
let_age = new WeakMap ();class Person { constructor (age) {_age.set (this, age); } incrementAge (){ 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 map = new WeakMap ();let el = document.getElementById ('someElement');// Store a weak reference to the element with a keymap.set (el, 'reference');// Access the value of the elementlet value = map.get (el); // 'reference'// Remove the referenceel.parentNode.removeChild (el);el = null;// 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 }); }); }); });});
на вертикальний:
func1 (value1) .then (func2) .then (func3) .then (func4) .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) => { if (body) { resolve (JSON.parse (body)); } else { resolve ({}); } });});
Ми також можемо розпаралелювати обіцянки для обробки масиву асинхронних операцій, використовуючи Promise.all ()
:
let urls =[ '/api/commits', '/api/issues/opened', '/api/issues/assigned', '/api/issues/completed',
'/api/issues/comments', '/api/pullrequests'];let promises = urls.map ((url) => { return new Promise ((resolve, reject) => { $.ajax ({ url: url }) .done ((data) => { resolve (data); }); });});Promise.all (promises) .then ((results) => { // Do something with results of all our promises });
Генератори
Подібно до обіцянок, що дозволяють нам уникнути пекла колбеків, генератори дозволяють згладити код, надаючи асинхронному коду синхронний вигляд. Генератори – це функції, які можуть призупинити своє виконання і згодом повернути значення виразу.
Простий приклад використання наведений нижче:
function* sillyGenerator (){ yield 1; yield 2; yield 3; yield 4;}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 Generatorsfunction request (url) { getJSON (url, function (response) { generator.next (response); });}
Ось функція-генератор, що повертає наші дані:
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);}
Завдяки силі yield
ми можемо бути впевненими, що в entry1
будуть потрібні дані, передані в data1
.
Проте для обробки помилок доведеться щось вигадати. Можна використати Promises:
function request (url) { 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);});
Async Await
Хоча ця функція з’явиться тільки в ES2016, async await
дозволяє нам робити те саме, що і в попередньому прикладі, але з меншими зусиллями:
var request = require ('request');function getJSON (url) { return new Promise (function (resolve, reject) { request (url, function (error, response, body) { resolve (body); }); });}async function main (){ var data = await getJSON (); console.log (data); // NOT undefined!}main ();
По суті, це працює так само, як і генератори, але використати краще саме цю функцію.
Гетери і сетери
ES6 привнесла підтримку гетерів і сетерів. Ось приклад:
class Employee { constructor (name) { this._ name = name; } get name (){ if (this._ name) { return 'Mr. ' + this._ name.toUpperCase (); } else { return undefined; } } set name (newName) { if (newName == this._ name) { console.log ('I already have this name.'); } else if (newName) { this._ name = newName; } else { return false; } }}var emp = new Employee ("James Bond");// uses the get method in the backgroundif (emp.name) { console.log (emp.name); // Mr. JAMES BOND}// uses the setter in the backgroundemp.name = "Bond 007";console.log (emp.name); // Mr. BOND 007
Свіжі браузери також дозволяють використовувати гетери / сетери в об’єктах, і тоді їх можна використати для обчислених властивостей, додаючи слухачів перед ними:
var person ={ firstName: 'James', lastName: 'Bond', get fullName (){ 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 Bondperson.fullName = 'Bond 007';person.fullName; // Bond 007
Символи
Символи були і до ES6, але тепер став доступний публічний інтерфейс для їх прямого використання. Символи незмінні й унікальні, їх можна використати як ключі будь-якого хеша.
Symbol ( )
Виклик Symbol ()
чи Symbol (description)
створить унікальний символ, недоступний глобально. Symbol ()
зазвичай використовується для додавання своєї логіки в сторонні об’єкти або простори імен, але треба бути обережним з подальшими оновленнями цих бібліотек. Наприклад, якщо Ви хочете додати метод refreshComponent
у клас React.Component
, переконайтеся, що він не збігається з методом, доданим у наступному оновленні:
const refreshComponent = Symbol ();React.Component.prototype[refreshComponent] = () => { // do something}
Symbol.for (key)
Symbol.for (key)
створить символ, який як і раніше буде незмінним і унікальним, але доступним глобально. Два ідентичні виклики Symbol.for (key)
повернуть ту саму сутність Symbol.
Примітка. Це неправильно для
Symbol (description)
:
Symbol ('foo') === Symbol ('foo') // falseSymbol.for ('foo') === Symbol ('foo') // falseSymbol.for ('foo') === Symbol.for ('foo') // true
Символи, зокрема, Symbol.for (key)
, зазвичай використовують для інтероперабельності, здійснюючи пошук символьного поля в аргументах стороннього об’єкта з відомим інтерфейсом, наприклад:
function reader (obj) { const specialRead = Symbol.for ('specialRead'); if (obj[specialRead]) { const reader = obj[specialRead](); // do something with reader } else { throw new TypeError ('object cannot be read'); }}
А в іншій бібліотеці:
const specialRead = Symbol.for ('specialRead');class SomeReadableType { [specialRead]() { const reader = createSomeReaderFrom (this); return reader; }}
Як приклад використання символів для інтероперабельності варто назвати
Symbol.iterator
, наявний в усіх ітерированих типах ES6: масивах, рядках, генераторах і т. д. При запуску методу повертається об’єкт з інтерфейсом ітератора.
До речі, ознайомитися з прикладами використання всіх нових можливостей можна, пройшовши навчальний відеокурс з JavaScript, про який ми писали раніше.
За матеріалами es6-cheatsheet
Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?