Є ідея гри чи геймінг-сервісу? Реєструйся на онлайн-хакатон 7.08! Призовий фонд — $3000
×Закрыть

Майбутнє JavaScript та ES2020-2021

Останні роки JavaScript розвивається дуже швидко, а починаючи за 2015 року він невпинно розвивається з року в рік і стає більш гнучким.

Нові версії специфікації ECMAScript доповнюються кожного року і нові пропозиції для змін фіналізуються швидше ніж можна колись було уявити. Так, багато нового було запропоновано та включено і у новий стандарт ES2020.

Якщо повернутися до історії, все почалось зі змагання, яке було назване «війною браузерів». Ця війна, яка відбулась в 90-х роках між компаніями Microsoft (Internet Explorer) та Netscape Communications (Navigator), залоложила початок виникнення специфікації ECMAScript.

Специфікація ECMAScript є стандартизованою специфікацією мови сценаріїв, розробленої Бренданом Айхом з Netscape. Перше видання ECMA-262 було прийнято у червні 1997 року. З того часу було видано кілька видань мовного стандарту.

Після першого видання, у наступних роках послідували дві інші версії до четвертого доповнення, яке було, на жаль, відкинуте і не реалізоване.

Після 10 річного затишшя, з’явилась п’ята версія, ES5, яка виявилась найбільш розширеною і принесла кардинальні зміни, а з шостою версією, починаючи з 2015 року, специфікацію було вирішено доповнювати кожного року.

Таким чином, нову версію реалізовувують, у всіх браузерах, цього року — у 2020му.

JavaScript — це реалізація специфікації ECMAScript. Це означає, що по мірі того, як у специфікації появляются нові чорновики (про них докладніше розкажу нижче) чи опубліковані редакції, розробники браузерів та платформ, типу Node.js, повинні послідовно вбудовувати новий функціонал.

Для цього вносятся зміни в рушіях (engines), які браузери та платформи використовують для інтерпретації і виповнення коду JavaScript.

Можливо у вас виникло питання, навіщо взагалі потрібна специфікація ECMAScript?

В різних браузерах використовуються різні рушії JavaScript, на кшталт, V8 в Chrome, SpiderMonkey в Firefox, та ін. Тобто, коли ви пишете на JavaScript, вы очікуєте, що всі рушії у всіх середовищах будуть реалізовувати ваш код абсолютно одинаково. Але, без стандартизованої специфікації будь який з рушіїв реалізовував би ваш код на JavaScript як йому заманеться. І це, на жаль, для нас це було б катастрофою, гіршого масштабу чим підтримка Internet Explorer :)

Якщо заглибитись в процес прийняття рішень щодо нових змін у JavaScript. То від ідеї до публікації пропозиції проходит, звісно, певний час, та декілька важливих стадій.
Вони пронумеровані з 0 до 4. Думаю, можливо це пов’язано з тим що відрахунок з нуля для нас це вже норма))

0: strawman — Це представлення ідеї.

1: proposal — Пропозиція, що відстоюється хоча б одним членом комітету TC39.

2: draft — Початкова версія специфікації з двома експериментальними реалізаціями.

3: candidate — Специфікація пропозиції переглядається і збираються коментарі що до нього від комітету TC39.

4: finished — Пропозиція готова до включення в ECMAScript.

Перехід на буль-яку наступну стадію потребує згоди технічного комітету TC39. Якщо ж пропозиція потрапила на стадію 4, можна очікувати, що її включать в наступну офіційну опубліковану редакцію специфікації стандарту ECMA-262, і вже тоді вона з’явиться в оточенні, де виконується JavaScript.
ES2020.

Так, що нового в четвертій, фінальній, стадії в ES2020?

import.meta

import.meta — це об’єкт, який вказує мета-дані JavaScript модуля в залежності від контексту. Він містить інформацію про модуль. Наприклад у нас є модуль module.js

<script type="module" src="module.js"></script>

import.meta дає можливість отримати об’єкт з мета-інформацією про цей модуль.

console.log(import.meta); // { url: "file:///home/app/module.js" }

String.protype.matchAll

Метод String.prototype.matchAll() має відношення до регулярних виразів.

Він повертає ітератор, яки дає змогу працювати зі всіма співпадіннями, які були знайдені в строці з використанням регулярних виразів, включаючи групи.

let regexp = /t(e)(st(\d?))/g;
let str = 'test1test2';

let array = [...str.matchAll(regexp)];

console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]

console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]

Метод Promise.allSettled

Метод Promise.allSettled повертає проміс тільки після того, як всі наявні проміси були завершені. Мається на увазі, що проміс завершений, він не знаходиться в очікуванні, тобто або виповнився, або ж відхилений.

Досить поширеним є бажання знати, що всі запити відбулись, не в залежності від результату. Це важливо, коли хочеться в майбутньому зайнятись поступовим покращенням.

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];

Promise.allSettled(promises).
  then((results) => results.forEach((result) => console.log(result.status)));

// expected output:
// "fulfilled"
// "rejected"

BigInt

BigInt, досить очікуване оновлення JavaScript так як це сьомий примітивний тип, ціле число довільної довжини. Теперь змінні данного типу можуть бути із 253 числових знаків, не обмежені числовим значенням 9007199254740992.

Що правда, такий тип даних не є обернено сумісний з тим, що було в мові раніше. Стандарт IEEE 754, на основі якого була робота з числами в JavaScript, не підтримує такі числа, робота з якими можлива дякуючи BigInt.

Найбільше безпечне ціле числове значення у JavaScript це 2^53.Це число можна отримати за допомогою MAX_SAFE_INTEGER.

const maxNumber = Number.MAX_SAFE_INTEGER;

console.log(maxNumber); // 9007199254740991

А ось такий приклад геть непередбачуваний:

console.log(maxNumber + 1); // 9007199254740992
console.log(maxNumber + 2); // 9007199254740992
console.log(maxNumber + 3); // 9007199254740994

Тепер ми можемо це обійти з новим типом BigInt.

Добавляючи літеру ’n’ в кінець числа, ми можемо використовувати і проводити ітерацію з шалено великими числами.

const bigNumber = 100000000000000000000000000000n;

console.log(bigNumber * 5n); // 500000000000000000000000000000n

Dynamic import

Динамічні імпорти в JavaScript дають можливість імпортувати JS файли динамічно, як модулі: нативно, у вашу аплікацію.

Це щось схоже як ми це робимо за допомогою Webpack та Babel. Великий плюс у цьому, що імпортування модулів, не засмічує глобальний простір.

if(newContition) {
const module = await import('./mydynamicmodule.js');
module.addNumbers(3, 2, 74);
}

Optional Chaining (опціональний ланцюжок)

Опціональні ланцюжки, на мою думку, одна з самих чудових можливостей як дає стандарт ES2020.

Вони дозволяють організувати безпечний доступ до глибоко вкладених властивостей об’єктів без необхідності перевірки існування кожного з них.

Якщо ж властивість яку ми перевіряємо не існує — нам вернеться undefined.

let nestedProp = obj.first?.second;

При використанні оператора ?. JavaScript виконує неявну перевірку, щоб перевірити, чи obj.first не дорівнює undefined чи null, перед тим, як звертатись до obj.first.second. Якщо obj.first дорівнює null чи undefined, вираз автоматично виконує коротке замикання, повертаючи undefined. І на такому прикладі, можна побачити, що це чудова заміна тернарного оператора.

let nestedProp = ((obj.first == null || obj.first == undefined) ? undefined : obj.first.second);

Опціональні ланцюжки працюють не лише зі властивостями об’єкта, а й з функціями та масивами. А ще добре працювати з querySelector

Ось як на цьому прикладі:

let myId = document .querySelector('#id') ?.innerText ?.replaceAll('foo', 'bar')

Так, дуже зручна річ!

Оператор Nullish Coalescing

Цей оператор вже давно є в інших мовах програмування, але тепер він нарешті з’явився і в JavaScript.

Nullish coalescing дає можливість перевірити null-подібні значення замість перевірки false-значень.

Але тут може виникнути питання, в чому ж різниця між null- и false-значеннями?

В JavaScript багато значень є false-подобні: це може бути значення 0 в number, null, false, NaN, або ж просто пустий рядок.

Але у багатьох випадках повинна бути перевірка на те, що вираз рівний лише null або undefined.

Наприклад, коли допускається, що змінна може бути пустим рядком, числом 0 або ж false.

І тут оператор nullish coalescing спішить на допомогу!

const foo = null ?? 'default string';
console.log(foo);
// expected output: "default string"

const baz = 0 ?? 42;
console.log(baz);
// expected output: 0

Глобальна властивість globalThis

Глобальна властивість globalThis зберігає посилання на глобальний об’єкт, який відповідає оточенню, в якому спрацьовує код.

В браузері глобальний об’єкт представлений об’єктом window.

В Node.js — це об’єкт global.

В веб-воркерах — self.

До globalThis, єдиним надійним варіантом отримати глобальний об’єкт був Function('return this')().

Але, це була причина CSP порушень в певних налаштуваннях, тому es6-shim використовує додаткову перевірку, як на такому прикладі:

var getGlobal = function () { 
if (typeof self !== 'undefined') { return self; } 
if (typeof window !== 'undefined') { return window; } 
if (typeof global !== 'undefined') { return global; } 
throw new Error('unable to locate global object'); }; 
var globals = getGlobal(); 
if (typeof globals.setTimeout !== 'function') { 
// нема setTimeout в даному оточенні! }

За допомогою globalThis, це вже не потрібно більше

if (typeof globalThis.setTimeout !== 'function') { 
// нема setTimeout в даному оточенні! }

For-in Mechanics

ECMA-262 залишає порядок for (a in b) майже повністю не визначеним, але в дійсності, рушії мають тенденцію бути послідовними, принаймні в деяких випадках.

Так історично виходило, що неодноразово провалювались обговорення щодо повної специфікації порядку for-in. Частково це пояснювалось тим, що всі рушії мають власні своєрідні реалізації і це створювало труднощі.

В кінці, було погоджено те, як ітерируються властивості при використанні for (a in b), щоб поведінка була стандартизованою.

Докладніше можна прочитати на офіційному гітхабі комітету.

ES2021: Що нас чекає в наступному році

String.prototype.replaceAll

На час написання статті, це було єдине оновлення, яке є вже включене в наступну специфікацію ES2021. Метод replaceAll() повертає новий рядок, в якому усі збіги з шаблоном змінені вказаним вами параметрами заміни. Такий шаблон може бути або рядком або ж регулярним виразом, а заміна може бути як рядком як і функцією, що викликається для кожного такого збігу.

const p = 'The quick brown fox jumps over the lazy dog. If the dog reacted, was it really lazy?';
const regex = /dog/gi;

console.log(p.replaceAll(regex, 'ferret'));
// expected output: "The quick brown fox jumps over the lazy ferret. If the ferret reacted, was it really lazy?"

console.log(p.replaceAll('dog', 'monkey'));
// expected output: "The quick brown fox jumps over the lazy monkey. If the monkey reacted, was it really lazy?"
 

Що нас чекає далі

Як ми знаємо стадія 3, має у собі список кандидатів які, вже зовсім скоро мають також увійти до майбутніх релізів ECMAScript. Деякі з них вже покриті тестами, тому такі будуть найшвидше розглядатись для релізу. Чи можна вже зараз користуватись функціоналом із майбутньої редакції ECMAScript? Так, це можливо! Частина нових можливостей вже є в деяких браузерах чи в Node.JS, але багато інших потребують стороннього транскомпілятора ECMAScript.

Ось список тих можливостей які наразі є на третій стадії, та вже покриті тестами — Hashbang Grammar. Такий коментар поводиться так само, як коментар (//). Він дозволений тільки на початку скрипта. Але починається з знаків #!, при цьому не можна ставити ніяких пробільних знаків перед ним.

#!/usr/bin/env node
// in the Script Goal
'use strict';
console.log(1);
 
#!/usr/bin/env node
// in the Module Goal
export {};
console.log(1);

Такий стиль треба використовувати тільки, щоб вказати інтерпретатор JavaScript.

У всіх інших випадках треба просто ставити звичайний коментар (//)

Numeric separators

Деякі мови програмування, як наприклад Java або C++, вже давно дають можливість використовувати такі числові роздільники. Щоб покращити читабельність, тепер можна згрупувати числа наприклад на тисячі: 1_000_000_000

І не тільки на тисячі, а можна використовувати у багатьох інших випадках.

Наприклад число в сантиметрах, числове розділення допомагає для читабельності в метрах 10_00 // 10 meters

А чи не було б добре, щоб таке було і в JavaScript?

Безумовно! І скоро це буде доступно в наступній специфікації ES. А з травня 2020 року, вже є доступно в більшості браузерів (Chrome, Firefox, Edge, Safari). А якщо працюєш з Node.js то у версії v12.5.0 також вже є. І в Електроні, звісно :)

const decimal = 1_234;
const binary = 0b1000_0101;
const hex = 0x12_34_56_78;
 
Top-level await
Рік назад таку можливість добавили у V8 для ES модулів.
 А зараз, Top-level await, на 3 стадії, щоб добавити в наступну специфікацію ES.
До цього, ми використовували await лише всередині async функції.
(async () => {
  await fetch(/* ... */)
})()

const doSomething = async () => {
  await fetch(/* ... */)
}

doSomething()
 
Top-level await дає можливість використовувати await без async
await fetch(/* ... */)
 
const strings = await import(`/i18n/${navigator.language}`);
const connection = await dbConnector();

WeakRefs

В JavaScript, garbage collector видаляє об’єкт, як тільки на них перестають посилатися. Такий підхід добре працює для повсякденних задач, але бувають задачі в яких таке поводження не підходить. До прикладів таких задач можна віднести імплементацію кешів чи Map (мапінги) який має в собі велику кількість об’єктів (наприклад необхідно зберігати в Map пару із імені файла і бінарних даних).

В ES6 для таких задач ввели WeakMap і WeakSet, але крім цього в JS на стадії 3 з’явився WeakRef який дозволяє створити слабе посилання на об’єкт. Слабкі посилання на відміну від звичайних, не перешкоджають виділенню об’єкта із пам’яті, що дозволяє перешкодити переповнення пам’яті при зберіганні великої кількості об’єктів і допомагає вирішити задачі описані вище.

function makeWeakCached(f) {
  const cache = new Map();
  return key => {
    const ref = cache.get(key);
    if (ref) {
      const cached = ref.deref();
      if (cached !== undefined) return cached;
    }

    const fresh = f(key);
    cache.set(key, new WeakRef(fresh));
    return fresh;
  };
}

var getImageCached = makeWeakCached(getImage);

RegExp Match Indices

Тепер при роботі з регулярними виразами, крім співпадіння також доступна інформація про позицію де було співпадіння в тексті. Інформація про позиції для кожного співпадіння доступне в поле .indices

let re = /javascript is ([A-Za-z]+)/i;
let result = re.exec('javascript is awesome');
console.log(result, result.indices);

// ["javascript is awesome", "awesome"]
// [[0, 21], [14, 21]]

На даний момент фіча доступна тільки за допомогою поліфіла, і в Google Chrome через опцію --harmony-regexp-match-indices

Promise.any

Promise.any приймає масив промісів і повертає проміс який виповнився після того, як перший промісів із масиву був завершений, на відміну від Promise.all, коли всім проміси з масиву повинні бути завершеними. Promise.any повертає помилку в тому випадку, коли всі проміси з масива також вернули помилку, на відміну від Promise.all де помилка була в першому промісі з масиву.

Promise.any(promises).then(
  (first) => {
    // Any of the promises was fulfilled.
  },
  (error) => {
    // All of the promises were rejected.
  }
);

Logical Assignment Operators

Ця можливість дає поєднати логічний оператор та вирази присвоювання (Assignment Expressions). Можна використовувати логічні присвоювання із новими операторами &&=, ||=, and ??= 

Експериментальна імплементація логічних присвоювань вже є доступна у V8 v8.4 через опцію --harmony-logical-assignment

// "Or Or Equals" (or, the Mallet operator :wink:)
a ||= b;
a || (a = b);

// "And And Equals"
a &&= b;
a && (a = b);

// "QQ Equals"
a ??= b;
a ?? (a = b);

Заключення

JavaScript це жива та широко розповсюджена мова, яка розширює можливості веб розробки. Нові можливості JavaScript, на мою думку, будуть досить корисними для вашої веб аплікації. Думаю, це хороший варіант позбутись декількох застарілих хаків і писати набагато менше коду ніж було раніше :)

Матеріали

https://flaviocopes.com/javascript-await-top-level/
https://github.com/tc39/ecma262
https://github.com/tc39/proposals
https://github.com/tc39/proposals/blob/master/finished-proposals.md

LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Optional Chaining (опціональний ланцюжок)
Опціональні ланцюжки, на мою думку, одна з самих чудових можливостей як дає стандарт ES2020.

О, Элвисов подвезли. От это счастье.

Чому коли я читав статтю мене не покидало відчуття, що JSу нічого не допоможе?

Исторические заболевания есть у многих ЯПов

кресты, PHP или Java с ее геттерами-сеттерами (хотя есть ломбок)

слышу дикий вой за стеной, или сосед мучает собаку или читает эту статью

Нові можливості будуть і надалі
blogs.oracle.com/...​comes-an-ecma-tc39-member

Поправте помилики в деяких місцях замість, ’що’ використовується ’что’.

Когда будет пайп оператор?!

Без карирования и композиции какой от него толк?

А нащо воно потрібно якщо є тайпскрипт?

занадто товсто, спробуй ще

setInterval(=>{console.log("

А нащо воно потрібно якщо є тайпскрипт?

")}, 1000):

А все же % его юзания, судя по вакансиях, в последние года безбожно вырос и воспринимается как «пофиксеный js для взрослых», т.е логическая и необходимая эволюция ЯП с динам. типизацией. По фронту прирост за счет проталкивания ангулярами, у бек прирост за счет все тех же свитчеров с джавы и это не смотря на то, что кучу хипстерских плюшек уже завезли в стандарт.

Вообще не толсто. Много лет кодил энтерпрайз на ES5+jQuery, потом неожиданно перевели на проект на ангуларе. Там ессесно все было до последней строчки на TS. Сейчас снова пишу на ES5+jQuery. Кому нужны те новые надуманные фичи JS я хз.

Приблизно 6 років тому я так само думав про CoffeeScript. Зараз теж пишу на TS, тільки він не в ES5 транспілиться.

Подписаться на комментарии