Огляд базових можливостей ES6

JavaScript сильно змінився за останні роки. Ось 12 нових можливостей, які можна почати використовувати вже сьогодні!
Історія
Нові додавання в мову називаються ECMAScript 6. Або ES6 або ES2015+.
З моменту появи в 1995, JavaScript розвивався повільно. Нові можливості додавалися кожні кілька років. ECMAScript з'явився в 1997, його метою було спрямувати розвиток JavaScript в потрібне русло. Виходили нові версії – ES3, ES5, ES6 і так далі.

Як бачите, між версіями ES3, ES5 і ES6 є пропуски довжиною в 10 і 6 років. Нова модель – робити маленькі зміни кожен рік. Замість того, щоб накопичити величезну кількість змін і випустити їх все за раз, як це було з ES6.
Browsers Support
Всі сучасні браузери і середовища виконання вже підтримують ES6!

Chrome, MS Edge, Firefox, Safari, Node і багато інші системи мають вбудовану підтримку більшості можливостей JavaScript ES6. Так що, всі з цього посібника можна використовувати прямо зараз.
Поїхали!
Головні можливості ES6
Всі фрагменти можна вставляти в консоль браузера і запускати.
Block scope variables
У ES6 ми перейшли від
var
на
let
/
const
.
Що не так з
var
?
Проблема
var
в тому, що змінна "протікає" в інші блоки коду, такі як цикли
for
або блоки умов
if
:
ES5
var x = 'outer';
function test(inner) {
if (inner) {
var x = 'inner'; // scope whole function
return x;
}
return x; // gets redefined on line 4
}

test(false); // undefined 
test(true); // inner

В рядку
test(false)
можна очікувати повернення
outer
, але ні, ми отримуємо
undefined
. Чому?
Тому що навіть не дивлячись на те, що блок
if
не виконується, на 4й рядку відбувається перевизначення
var x
undefined
.
ES6 поспішає на допомогу:
ES6
нехай x = 'outer';
function test(inner) {
if (inner) {
нехай x = 'inner';
return x;
}
return x; // gets result from line 1 as expected
}

test(false); // outer
test(true); // inner

Змінивши
var
на
let
ми відкоригували поведінку. Якщо блок
if
не викликається, то змінна
x
не змінюється.
IIFE (immediately invoked function expression)
Давайте спочатку розглянемо приклад:
ES5
{
var private = 1;
}

console.log(private); // 1

Як бачите,
private
протікає назовні. Потрібно використовувати IIFE (immediately-invoked function expression):
ES5
(function(){
var private2 = 1;
})();

console.log(private2); // Uncaught ReferenceError

Якщо поглянути на jQuery/lodash або будь-які інші проекти з відкритим вихідним кодом, то можна помітити, що там IIFE використовується для утримання глобального середовища в чистоті. А глобальні штуки визначаються зі спеціальними символами начебто
_
,
$
або
jQuery
.
У ES6 не потрібно використовувати IIFE, досить використовувати блоки та
let
:
ES6
{
let private3 = 1;
}

console.log(private3); // Uncaught ReferenceError

Const
також Можна використовувати
const
якщо змінна не повинна змінюватися.
Підсумок:
  • забудьте
    var
    , використовуйте
    let
    та
    const
    .
  • Використовуйте
    const
    для всіх референсов; не використовуйте
    var
    .
  • Якщо референси потрібно змінити, використовуйте
    let
    замість
    const
    .
Template Literals
Не потрібно більше робити вкладену конкатенацию, можна використовувати шаблони. Подивіться:
ES5
var first = 'Fred';
var last = 'Mejia';
console.log('Your name is' + first + '' + last + '.');

З допомогою бэктика (
) та інтерполяції рядків 
${}` можна зробити так:
ES6
const first = 'Fred';
const last = 'Mejia';
console.log(`Your name is ${first} ${last}.`);

Multi-line strings
Не потрібно більше конкатенувати рядки з +
\n
:
ES5
var template = '<li *ngFor="let all of todos" [ngClass]="{completed: todo.isDone}" >\n' +
'<div class="view">\n' +
'<input class="toggle" type="checkbox" [checked]="todo.isDone">\n' +
'<label></label>\n' +
'<button class="destroy"></button>\n' +
'</div>\n' +
'<input class="edit" value="">\n' +
'</li>';
console.log(template);

У ES6 можна знову використовувати бэктики:
ES6
const template = `<li *ngFor="let all of todos" [ngClass]="{completed: todo.isDone}" >
<div class="view">
<input class="toggle" type="checkbox" [checked]="todo.isDone">
<label></label>
<button class="destroy"></button>
</div>
<input class="edit" value="">
</li>`;
console.log(template);

Обидва блоки коду генерують однаковий результат
Destructuring Assignment
ES6 desctructing – корисна і лаконічна штука. Подивіться на приклади:
Отримання елемента з масиву
ES5
var array = [1, 2, 3, 4];

var first = array[0];
var third = array[2];

console.log(first, third); // 1 3

Те ж саме:
ES6
const array = [1, 2, 3, 4];

const [first, ,third] = array;

console.log(first, third); // 1 3

Обмін значеннями
ES5
var a = 1;
var b = 2;

var tmp = a;
a = b;
b = tmp;

console.log(a, b); // 2 1

Те ж саме:
ES6
let a = 1;
let b = 2;

[a, b] = [b, a];

console.log(a, b); // 2 1

Деструктуризация декількох значень
ES5
function margin() {
var left=1, right=2, top=3, bottom=4;
return { left: left, right: right, top: top, bottom: bottom };
}

var data = margin();
var left = data.left;
var bottom = data.bottom;

console.log(left, bottom); // 1 4

У рядку 3 можна повернути у вигляді масиву:
return [left, right, top, bottom];

але викликає кодом доведеться знати про порядок даних.
var left = data[0];
var bottom = data[3];

З ES6 викликає вибирає потрібні дані (рядок 6):
ES6
function margin() {
const left=1, right=2, top=3, bottom=4;
return { left, right, top, bottom };
}

const { left, bottom } = margin();

console.log(left, bottom); // 1 4

Примітка: У рядку 3 містяться інші можливості ES6. Можна скоротити
{ left: left }
{ left }
. Дивіться, наскільки це лаконічніше у порівнянні з версією ES5. Круто ж?
Деструктуризация і зіставлення параметрів
ES5
var user = {firstName: 'Fred', lastName: 'Mejia'};

function getFullName(user) {
var firstName = user.firstName;
var lastName = user.lastName;

return firstName + '' + lastName;
}

console.log(getFullName(user)); // Adrian Mejia

Те ж саме (але коротше):
ES6
const user = {firstName: 'Fred', lastName: 'Mejia'};

function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}

console.log(getFullName(user)); // Adrian Mejia

Глибоке зіставлення
ES5
function settings() {
return { display: { color: 'red' }, keyboard: { layout: 'querty'} };
}

var tmp = settings();
var displayColor = tmp.display.color;
var keyboardLayout = tmp.keyboard.layout;

console.log(displayColor, keyboardLayout); // red querty

Те ж саме (але коротше):
ES6
function settings() {
return { display: { color: 'red' }, keyboard: { layout: 'querty'} };
}

const { display: { color: displayColor }, keyboard: { layout: keyboardLayout }} = settings();

console.log(displayColor, keyboardLayout); // red querty

Це також називають деструктуризацией об'єкта (object destructing).
Як бачите, деструктуризация може бути дуже корисною і може підштовхувати до поліпшення стилю кодування.
Поради:
  • Використовуйте деструктуризацию для отримання елементів з масиву та для обміну значеннями. Не потрібно робити тимчасові референси – заощадите час.
  • Не використовуйте деструктуризацию масиву для декількох значень, замість цього використовуйте деструктуризацию об'єкта.
Класи і об'єкти
В ECMAScript 6 ми перейшли від «функцій-конструкторів» до «класами» .
Кожен об'єкт JavaScript має прототип, який є іншим об'єктом. Всі об'єкти в JavaScript успадковують методи і властивості від свого прототипу.
В ES5 об'єктно-орієнтоване програмування досягалося з допомогою функцій-конструкторів. Вони створювали об'єкти наступним чином:
ES5
var Animal = (function () {
function MyConstructor(name) {
this.name = name;
}
MyConstructor.prototype.speak = function speak() {
console.log(this.name + ' makes a noise.');
};
return MyConstructor;
})();

var animal = new Animal('animal');
animal.speak(); // animal makes a noise.

У ES6 є новий синтаксичний цукор. Можна зробити те ж саме з меншим кодом і з використанням ключових слів
class
та
construсtor
. Також зауважте, як чітко визначаються методи:
construсtor.prototype.speak = function ()
vs
speak()
:
ES6
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}

const animal = new Animal('animal');
animal.speak(); // animal makes a noise.

Обидва стилю (ES5/6) дають однаковий результат.
Поради:
  • Завжди використовуйте синтаксис
    class
    та не змінюйте
    prototype
    . Код лаконічніше і його буде легше зрозуміти.
  • Уникайте створення порожнього конструктора. У класів є конструктор за замовчуванням, якщо не задати власний.
Спадкування
Давайте продовжимо попередній приклад з класом
Animal
. Припустимо, нам потрібен новий клас
Lion
.
У ES5 доведеться трохи попрацювати з прототипным спадкуванням.
ES5
var Lion = (function () {
function MyConstructor(name){
Animal.call(this, name);
}

// prototypal inheritance
MyConstructor.prototype = Object.create(Animal.prototype);
MyConstructor.prototype.constructor = Animal;

MyConstructor.prototype.speak = function speak() {
Animal.prototype.speak.call(this);
console.log(this.name + ' roars ');
};
return MyConstructor;
})();

var lion = new Lion('Simba');
lion.speak(); // Simba makes a noise.
// Simba roars.

Не будемо вдаватися в деталі, але зауважте кілька деталей:
  • Рядок 3, безпосередньо викликаємо конструктор
    Animal
    з параметрами.
  • Рядки 7-8, призначаємо прототип
    Lion
    прототипом класу
    Animal
    .
  • Рядок 11, викликаємо метод
    speak
    з батьківського класу
    Animal
    .
У ES6 є нові ключові слова
extends
та
super
.
ES6
class Lion extends Animal {
speak() {
super.speak();
console.log(this.name + ' roars ');
}
}

const lion = new Lion('Simba');
lion.speak(); // Simba makes a noise.
// Simba roars.

Подивіться, наскільки краще виглядає код на ES6 порівняно з ES5. І вони роблять одне і те ж! Win!
Порада:
  • Використовуйте вбудований спосіб спадкування –
    extends
    .
Нативні промисы
Переходимо від callback hell до промисам (promises)
ES5
function printAfterTimeout(string, timeout, done){
setTimeout(function(){
done(string);
}, timeout);
}

printAfterTimeout('Hello ', 2e3, function(result){
console.log(result);

// nested callback
printAfterTimeout(result + 'Reader', 2e3, function(result){
console.log(result);
});
});

Одна функція приймає callback щоб запустити після його завершення. Нам потрібно запустити її двічі, одну за одною. Тому доводиться викликати
printAfterTimeout
вдруге в коллбеке.
Все стає зовсім погано, коли потрібно додати третій або четвертий коллбек. Давайте подивимося, що можна зробити з промисами:
ES6
function printAfterTimeout(string, timeout){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve(string);
}, timeout);
});
}

printAfterTimeout('Hello ', 2e3).then((result) => {
console.log(result);
return printAfterTimeout(result + 'Reader', 2e3);

}).then((result) => {
console.log(result);
});

З допомогою
then
чи можна обійтися без вкладених функцій.
Стрілочні функції
У ES5 звичайні визначення функцій не зникли, але був доданий новий формат – стрілочні функції.
У ES5 є проблеми з
this
:
ES5
var _this = this; // need to hold a reference

$('.btn').click(function(event){
_this.sendData(); // reference outer this
});

$('.input').on('change',function(event){
this.sendData(); // reference outer this
}.bind(this)); // bind to outer this

Треба використовувати тимчасовий
this
щоб посилатися на нього всередині функції або використовувати
bind
. У ES6 можна просто використовувати стрелочную функцію!
ES6
// this will reference the outer one
$('.btn').click((event) => this.sendData());

// implicit returns
const ids = [291, 288, 984];
const messages = ids.map(value => `ID is ${value}`);

For...of
Від
for
переходимо до
forEach
а потім до
for of...
:
ES5
// for
var array = ['a', 'b', 'c', 'd'];
for (var i = 0; i < array.length; i++) {
var element = array[i];
console.log(element);
}

// forEach
array.forEach(function (element) {
console.log(element);
});

ES6 for...of дозволяє використовувати ітератори
ES6
// for of ...
const array = ['a', 'b', 'c', 'd'];
for (const element of array) {
console.log(element);
}

Параметри за замовчуванням
Від перевірки параметрів переходимо до параметрів за замовчуванням. Ви робили що-небудь таке?
ES5
function point(x, y, isFlag){
x = x || 0;
y = y || -1;
isFlag = isFlag || true;
console.log(x,y, isFlag);
}

point(0, 0) // 0 -1 true 
point(0, 0, false) // 0 -1 true 
point(1) // 1 -1 true
point() // 0 -1 true

Швидше за все так. Це поширений патерн перевірки наявності значення змінної. Але тут є деякі проблеми:
  • Рядок 8, передаємо
    0, 0
    отримуємо
    0, -1
  • Рядок 9, передаємо
    false
    , але отримуємо
    true
    .
Якщо цей параметр за замовчуванням це булева змінна або якщо задати значення 0, то нічого не вийде. Чому? Розповім після цього прикладу з ES6 ;)
У ES6 все виходить краще з меншою кількістю коду:
ES6
function point(x = 0, y = -1, isFlag = true){
console.log(x,y, isFlag);
}

point(0, 0) // 0 0 true
point(0, 0, false) // 0 0 false
point(1) // 1 -1 true
point() // 0 -1 true

Отримуємо очікуваний результат. Приклад ES5 не працював. Потрібно перевіряти на
undefined
так
false
,
null
,
undefined
та
0
– це все falsy-значення. З числами можна так:
ES5
function point(x, y, isFlag){
x = x || 0;
y = typeof(y) === 'undefined' ? -1 : y;
isFlag = typeof(isFlag) === 'undefined' ? true : isFlag;
console.log(x,y, isFlag);
}

point(0, 0) // 0 0 true
point(0, 0, false) // 0 0 false
point(1) // 1 -1 true
point() // 0 -1 true

З перевіркою на
undefined
все працює як треба.
Rest-параметри
Від аргументів до rest-параметрами та операції spread.
У ES5 працювати зі змінною кількістю аргументів незручно.
ES5
function printf(format) {
var params = [].slice.call(arguments, 1);
console.log('params: ', params);
console.log('format:' format);
}

printf ("%s %d %.2f', 'fred', 321, Math.PI);

З rest
...
все набагато простіше.
ES6
function printf(format ...params) {
console.log('params: ', params);
console.log('format:' format);
}

printf ("%s %d %.2f', 'fred', 321, Math.PI);

Операція Spread
Переходимо від
apply()
до spread. Знову ж таки,
...
поспішає на допомогу:
Пам'ятайте: ми використовуємо
apply()
щоб перетворити масив в список аргументів. наприклад,
Math.max()
приймає список параметрів, але якщо у нас є масив, то можна використовувати
apply
.
ES5
Math.max.apply(Math, [2,100,1,6,43]) // 100

У ES6 використовуємо spread:
ES6
Math.max(...[2,100,1,6,43]) // 100

Ми також перейшли від
concat
до spread'у:
ES5
var array1 = [2,100,1,6,43];
var array2 = ['a', 'b', 'c', 'd'];
var array3 = [false, true, null, undefined];

console.log(array1.concat(array2, array3));

У ES6:
ES6
const array1 = [2,100,1,6,43];
const array2 = ['a', 'b', 'c', 'd'];
const array3 = [false, true, null, undefined];

console.log([...array1, ...array2, ...array3]);

Висновок
JavaScript сильно змінився. Ця стаття покриває тільки базові можливості, про які повинен знати кожен розробник.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.