ECMAScript 6+

Коротка історія JavaScript / ECMAScript

JavaScript - товарний знак, що належить компанії Oracle Corporation в США.

ECMAScript належить організації ECMA (European Computer Manufacturers Association) - міжнародній організації, що займається регулюванням та стандартизацією у сферах IT та комунікації.

Початкова розробка була здійснена в компанії Netscape, за прототип було взято скриптову мову Cmm (C--, ScriptEase), яка так і не здобула популярності. Змінивши декілька назв (Mocha, LiveScript/LiveWire) в 1995 році мова отримала назву JavaScript (під ліцензією Sun Microsystems, яку в 2010 році викупила Oracle).

Microsoft в 1996 році випустив аналогічну скриптову мову JScript, вбудував його в IE3.

Організація ECMA стандартизувала мови JavaScript, JScript і ScriptEase, прийнявши ECMAScript. Ці три скриптові мови є взаємосумісними, проте були ньюанси, варіанти реалізації BOM і DOM (дивись далі), що призводили до проблем крос-браузерності.

Між першою та другою версіями було багато мінорних версій: 1.1, 1.2 ... 1.8

ECMAScript 2 вийшов в червні 1998 року.

ECMAScript 3 вийшов у грудні 1999 року.

Робота над четвертою версією заглохла на пів-дорозі, вона так і не вийшла через сварки в консорціумі розробників, патенти, конкуренцію та лобіювання власних інтересів. Проте, технології, декларовані в цій версії, були реалізовані у різних браузерах (неофіційно).

ECMAScript 5 вийшов у грудні 2009 року (переназвана версія 3.1, яку розробили на заміну 4-ї версії, відкинувши всі серйозні нововведення і зосередившись на оптимізації).

Разом з впровадженням HTML5 та CSS3, виникненням нових концепцій веб-застосунків, прискоренням розробки браузерів (від 0,2-1 версії на рік до майже щомісячного реліза) ECMAScript також прискорився, впровадив багато нових можливостей, взяв курс на зближення з іншими мовами програмування.

ECMAScript 6 / 2015 — березень 2015 року.

Браузери не встигали завершити свій життєвий цикл, багато користувачів з різних причин сиділи на старих браузерах, що не підтримували нові можливості. Тому набули популярність транспайлери (конвертори нового JS в старий), наприклад, Babel, Traceur.

ECMAScript 7 / 2016.

ECMAScript 8 / 2017.

ECMAScript 9 / 2018.

Таким чином, JavaScript створюють та супроводжують декілька компаній: ECMA, Oracle, Microsoft, Mozilla. На деяких етапах історії також долучалися Adobe, Opera, Yahoo та інші компанії.

Загальний огляд JavaScript

JavaScript складається з трьох компонентів:
- ядро (ECMAScript);
- об'єктна модель браузера (BOM);
- об'єктна модель документа (DOM).

Таким чином, якщо JS використовується у вбудованій техніці, робототехніці, як мова сценаріїв (макросів) у програмах, іграх, то використовується лише ядро і додаткові інтерфейси для роботи з портами вводу/виводу, файловою системою і т.д., без BOM і DOM. Ядро і віртуальна машина доволі компактні і дозволяють вшити їх в ROM, CMOS-чіпи невеликого об'єму та розміру.

BOM та DOM не стандартизуються організацією ECMA, специфікації розробляють спільноти WHATWG і W3C, а доки немає специфікацій - кожна з компаній-розробників ліпить свої варіанти реалізацій, що якраз і веде до проблем кросбраузерності.

Нові можливості ECMAScript 6 / 2015

Похилим шрифтом в коді показані класичні реалізації

Оголошення констант const та локальних змінних let

Специфікація радить відмовитися від використання var.

const COLUMNS_COUNT = 12;
for (let n = 1; n <= COLUMNS_COUNT; n++) {
  setTimeout(showMessage, n * 1000);
}
if (test) {
  let str = 'hello';
  ...
}

Змінні n та str будуть існувати лише в межах фігурних дужок

Змінна n буде доступна в асинхронній функції showMessage зі значеннями 1, 2, 3...

Оголошені змінні через let будуть доступні лише нижче по коду, в той час як змінні, оголошені через var будуть мати значення undefined вище по коду.

Відмова від IIFE (immediately invoked function expression) - як наслідок з області видимості let, const:

(function (){
  var x = 42;
})();


{
  let x = 42;
}

Дефолтні значення аргументів функції

function fun(a, b = 6, c = false) { ... }

function fun(a, b, c) {
  if (b === undefined) b = 6;
  if (c === undefined) c = false;
}

Іменовані аргументи функцій

function fun1({ x = 10, y }) { ... }
function fun2(s, b, { x, y }) { ... }
fun1({ y: 30 });
fun1({ x: 5, y: 42 });
fun1({ y: 42, x: 5 });
fun2('Вася', false, { x: 30, y: 0 });

Деструктуризація об'єктів (іменовані аргументи функцій якраз і використовують деструктуризацію).

let arr = [40, 20, 0];
let [x, y, z] = arr;
let [a, , c] = arr;
let { name: n, age: a } = { name: 'Вася', age: 22 };
n === 'Вася', a === 22.

Скорочений варіант з іменами змінних, що співпадають з іменами властивостей об'єкта (використовується розпаковування об'єкта):

let { name, age } = { name: 'Вася', age: 22 };
name === 'Вася', age === 22.

Корисна річ, зменшує писанину, покращує читаємість коду:

let name = 'Masha';
let age = 17;

var obj = { name: name, age: age, x: 4 };
let obj = { name, age, x: 4 };

var x = obj.x;
let {x} = obj;
console.log(x);    // 4

Порівняйте два старих способа і один новий:

function padding(){
  var top = '10px', right = '20px', bottom = '30px', left = '20px';
  return [ top, right, bottom, left ];
}
var pad = padding();
var left = pad[3], right = pad[1];

function padding(){
  var top = '10px', right = '20px', bottom = '30px', left = '20px';
  return { top: top, right: right, bottom: bottom, left: left };
}
var pad = padding();
var left = pad.left, right = pad.right;


function padding(){
  let top = '10px', right = '20px', bottom = '30px', left = '20px';
  return { top, right, bottom, left };
}
let { left, right } = padding();

Дозволяє досить просто обмінятися значеннями:

let a = 10, b = 25;

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


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

Spreads - зручне розпаковування масивів:

function fun(a, b, c, d) { alert(a + b + c + d); }
const arr = [3, 4, 5, 6];
fun(...arr);
const brr = [1, 2, ...arr, 7, 8];
alert('Max: ' + Math.max(...arr));

arr1 = arr1.concat(arr2, arr3);
arr1 = [...arr1, ...arr2, ...arr3];

А ще ці три крапки дозволяють скопіювати об'єкт:

let obj1 = { name: 'Вася', age: 22 };
let obj2 = { ...obj1 };

Стрілочні функції

Стрілочні функції завжди анонімні, не мають arguments та super, не можуть бути конструктором, не мають власного this, а використовують this з контексту вище.

() => { ... }
function (){ ... }

() => window.innerWidth / 2;
function (){ return window.innerWidth / 2; }

() => alert('hello');
function (){ return alert('hello'); }

(a, b, c) => { ... }
function (a, b, c){ ... }

(a, b, c) => a + b * c;
function (a, b, c){ return a + b * c; }

a => { ... }
a => a * a;
let obj = { sqr: a => a * a; }
obj.sqr(5);    // 25
var obj = {
  sqr: function(a){ return a * a; }
}


val => ({ str: val });
function(val){ return { str: val }; }

(a, b, ...c) => { ... }
(a = 5, b, c = 'hey') => { ... }
let f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
f();    // 6

function Person(){
  this.age = 0;
  setInterval(() => { this.age++; }, 1000);
}
let p = new Person();
function Person(){
  this.age = 0;
  var that = this;
  setInterval(function(){ that.age++; }, 1000);
}
var p = new Person();

Класи (синтаксичний цукор поверх прототипів)

JS рухається в бік повноцінного ООП.

class User {
  constructor(name, age){
    this.name = name;
    this.age = age;
  }
  hello(){
    alert('Hello ' + this.name);
  }
}
let user = new User('John', 25);
user.hello();

function User(name, age){
  this.name = name;
  this.age = age;
}
User.prototype.hello = function(){
  alert('Hello ' + this.name);
};
var user = new User('John', 25);
user.hello();


let User = class {
  sayHi() { alert('Привет!'); }
};
new User().sayHi();

// наслідування:
class Customer extends User {
  constructor(name, age, gender){
    super.constructor(name);
    this.gender = gender;
  }
  hello(){
    super.hello();
    console.log('gender: ' + this.gender);
  }
}

User, оголошений через class, неможна викликати без new (буде помилка).

Оголошення class веде себе як let: клас буде доступний лише в поточному блоці і лише нижче по коду.

Методи класа не злічені, вони не будуть доступні в конструкції for..in.

Можна створювати статичні методи (ці методи не мають доступу до this і можуть викликатися без створення об'єкта). Приклад вам вже відомий, метод now статичний:

let ms = Date.now();
let today = new Date();

class User {
  constructor(name, role){
    this.name = name;
    this.role = role;
  }
  static anon(){
    return new User('anon', 0);
  }
}
let admin = new User('admin', 7);
let anon = User.anon();

Покращення об'єктних літералів

let offsetX = '70px';
let n = 42;
let obj = {
  offsetX,
  ['item-' + n]: 'Sorok dva',
  hello42(){ alert(this['item-42']); }
};

var obj = {
  offsetX: offsetX,
  hello42: function(){ alert(this['item-42']); }
};
obj['item-' + n] = 'Sorok dva';

Модулі - продвинутий варіант бібліотеки функцій.

По-старому:

var Math = {
  PI: 3.14159,
  sin: function (angle){ return ... ; },
  cos: function (angle){ return ... ; },
  __internalFunction: function (){ ... }
}

По-новому:

module Math {
  export const PI = 3.14159,
  export let step = 0,
  export function sin(angle){ return ... ; }
  export function cos(angle){ return ... ; }
  internalFunction() { ... }
}

При цьому службова функція internalFunction ззовні буде недоступна.

Функції і змінні модуля можна імпортувати для більш зручного використання, область видомості імпорта обмежена конструкціями в фігурних дужках. Імпорт схожий на оператор with (не рекомендований до застосування):

let angleSin = Math.sin(angle * Math.PI / 180);

import Math.{sin, PI};
let angleSin = sin(angle * PI / 180);

import Math.*;
let angleSin = sin(angle * PI / 180);
let angleCos = cos(angle * PI / 180);
step++;
internalFunction();    // тут буде помилка

Цикл for-of

Зручний та очевидний перебір елементів масива:

for (var i = 0; i < arr.length; i++) { arr[i] - кожен елемент масива }
arr.forEach(function (e){ e - кожен елемент масива });
for (let e of arr) { e - кожен елемент масива }

Шаблонні літерали

Тепер для виокремлення строк можна використовувати не тільки одинарні та подвійні лапки, а й апостроф, що знаходиться під тільдою.

Строка в цих апострофах може містити шаблони зі змінними, виразами, викликами функцій:

const s = 'Masha';
alert('Hello ' + s + '!');
alert(`Hello ${s}!`);
alert(`Hello ${Math.sin(s.length) + 42}!`);

Багатострочні строки

var s1 = 'lorem ipsum dolor ' +
  'sit amet, consectetur ' +
  'adipisicing elit.';
var s2 = 'lorem ipsum dolor ';
s2 += 'sit amet, consectetur ';
s2 += 'adipisicing elit.';


let s = `lorem ipsum dolor
sit amet, consectetur
adipisicing elit.`;

Генератори yield

Добавлені функції-генератори (TODO: дописати).

Проміси, Fetch (TODO: дописати).

Нові можливості ECMAScript 7 / 2016

Array.prototype.includes(n) - перевіряє наявність елемента n в масиві, повертає true чи false.

Оператор піднесення до степеня

// 7 5 =
Math.pow(7, 5);

7**5

Нові можливості ECMAScript 8 / 2017

Асинхронні функції:

async function fun(){ ... }
const fun = async function (){ ... };
const fun = async () => { ... };
let obj = { async methodName(){ ... } };

Асинхронні функції повертають проміс, що виконається асинхронно, проте саме тіло функції виконується синхронно:

async function fun(){
  console.log('test');
  return 'Hello!';
}
console.log('start');
fun().then(function (n){ console.log(n); });
console.log('finish');

// результат:
start
test
finish
Hello!

Object.entries() - повертає масив з підмасивів, кожен підмасив - пара "ключ", "значення".

Object.values() - повертає масив значень ключів об'єкта.

Висячі коми після останнього елемента масива, об'єкта, аргумента функції.

Нові можливості ECMAScript 9 / 2018

Нові можливості ECMAScript X / 2019