Перейти к основному содержимому

39 записей с тегом "ECMAScript"

Посмотреть все теги

Слабые ссылки и финализаторы

· 9 мин. чтения
Сатья Гунасекаран ([@_gsathya](https://twitter.com/_gsathya)), Матиас Биненс ([@mathias](https://twitter.com/mathias)), Шу-ю Го ([@_shu](https://twitter.com/_shu)) и Лешек Свирски ([@leszekswirski](https://twitter.com/leszekswirski))

В общем случае ссылки на объекты в JavaScript являются сильно удерживаемыми, что означает, что пока у вас есть ссылка на объект, он не будет удален сборщиком мусора.

const ref = { x: 42, y: 51 };
// Пока у вас есть доступ к `ref` (или любой другой ссылке на
// тот же объект), объект не будет удален сборщиком мусора.

На данный момент WeakMap и WeakSet являются единственными способами частично слабой ссылки на объект в JavaScript: добавление объекта в качестве ключа в WeakMap или WeakSet не предотвращает его удаление сборщиком мусора.

const wm = new WeakMap();
{
const ref = {};
const metaData = 'foo';
wm.set(ref, metaData);
wm.get(ref);
// → metaData
}
// У нас больше нет ссылки на `ref` в этой области видимости блока, поэтому он
// может быть удален сборщиком мусора, даже если он является ключом в `wm`, к
// которому у нас все еще есть доступ.

Стабильная `Array.prototype.sort`

· 3 мин. чтения
Mathias Bynens ([@mathias](https://twitter.com/mathias))

Допустим, у вас есть массив собак, где каждая собака имеет имя и рейтинг. (Если это кажется странным примером, знайте, что существует аккаунт в Twitter, который специализируется именно на этом… Не спрашивайте!)

// Обратите внимание, что массив предварительно отсортирован по алфавиту по `name`.
const doggos = [
{ name: 'Abby', rating: 12 },
{ name: 'Bandit', rating: 13 },
{ name: 'Choco', rating: 14 },
{ name: 'Daisy', rating: 12 },
{ name: 'Elmo', rating: 12 },
{ name: 'Falco', rating: 13 },
{ name: 'Ghost', rating: 14 },
];
// Сортируем собак по рейтингу в порядке убывания.
// (Это обновляет `doggos` на месте.)
doggos.sort((a, b) => b.rating - a.rating);

`Symbol.prototype.description`

· 1 мин. чтения
Mathias Bynens ([@mathias](https://twitter.com/mathias))

В JavaScript для Symbol можно задать описание при создании:

const symbol = Symbol('foo');
// ^^^^^

Ранее единственным способом программно получить это описание был непрямой вызов Symbol.prototype.toString():

const symbol = Symbol('foo');
// ^^^^^
symbol.toString();
// → 'Symbol(foo)'
// ^^^
symbol.toString().slice(7, -1); // 🤔
// → 'foo'

Однако этот код выглядит немного магически, не очень очевиден и нарушает принцип «выражать намерение, а не реализацию». Кроме того, данный метод не позволяет отличить символ без описания (например, Symbol()) от символа с пустой строкой в качестве описания (например, Symbol('')).

`Object.fromEntries`

· 3 мин. чтения
Матиас Биненс ([@mathias](https://twitter.com/mathias)), шептун JavaScript

Object.fromEntries — это полезное дополнение к встроенной библиотеке JavaScript. Перед тем как объяснить, что он делает, важно понять существующий API Object.entries.

Object.entries

API Object.entries существует уже некоторое время.

Для каждой пары ключ-значение в объекте Object.entries предоставляет массив, где первый элемент — это ключ, а второй элемент — значение.

Object.entries особенно полезен в сочетании с for-of, так как он позволяет элегантно проходить по всем парам ключ-значение в объекте:

const object = { x: 42, y: 50 };
const entries = Object.entries(object);
// → [['x', 42], ['y', 50]]

for (const [key, value] of entries) {
console.log(`Значение ${key} равно ${value}.`);
}
// Результат:
// Значение x равно 42.
// Значение y равно 50.

К сожалению, нет простого способа преобразовать результат entries обратно в эквивалентный объект… до настоящего момента!

Object.fromEntries

Новый API Object.fromEntries выполняет обратное преобразование от Object.entries. Это делает легким восстановление объекта на основе его пар:

const object = { x: 42, y: 50 };
const entries = Object.entries(object);
// → [['x', 42], ['y', 50]]

const result = Object.fromEntries(entries);
// → { x: 42, y: 50 }

Один из частых случаев использования заключается в преобразовании объектов. Теперь вы можете делать это, проходя по его парам и используя методы массивов, которые вам уже знакомы:

const object = { x: 42, y: 50, abc: 9001 };
const result = Object.fromEntries(
Object.entries(object)
.filter(([ key, value ]) => key.length === 1)
.map(([ key, value ]) => [ key, value * 2 ])
);
// → { x: 84, y: 100 }

В этом примере мы фильтруем объект, чтобы оставить только ключи длиной 1, то есть только ключи x и y, исключая ключ abc. Затем мы преобразуем оставшиеся пары и возвращаем обновленные пары ключ-значение для каждой. В этом примере мы удваиваем каждое значение, умножая его на 2. Конечный результат — новый объект, содержащий только свойства x и y с новыми значениями.

Комбинаторы Promise

· 4 мин. чтения
Маттиас Биненс ([@mathias](https://twitter.com/mathias))

С момента появления промисов в ES2015 JavaScript поддерживал ровно два комбинатора promise: статические методы Promise.all и Promise.race.

Два новых предложения в настоящее время проходят процесс стандартизации: Promise.allSettled и Promise.any. С этими дополнениями в JavaScript будут доступны четыре комбинатора promise, каждый из которых позволяет решать свои задачи.

`Array.prototype.flat` и `Array.prototype.flatMap`

· 2 мин. чтения
Mathias Bynens ([@mathias](https://twitter.com/mathias))

Array.prototype.flat

Массив в этом примере имеет несколько уровней вложенности: он содержит массив, который, в свою очередь, содержит другой массив.

const array = [1, [2, [3]]];
// ^^^^^^^^^^^^^ внешний массив
// ^^^^^^^^ вложенный массив
// ^^^ самый глубокий массив

Array#flat возвращает одномерную версию многомерного массива.

array.flat();
// → [1, 2, [3]]

// …эквивалентно:
array.flat(1);
// → [1, 2, [3]]

Глубина по умолчанию равна 1, но можно указать любое число для рекурсивного свёртывания до этой глубины. Чтобы продолжать свёртывание рекурсивно, пока в результате не останется вложенных массивов, нужно передать Infinity.

// Рекурсивное свёртывание до полного удаления вложенных массивов:
array.flat(Infinity);
// → [1, 2, 3]

Почему этот метод называется Array.prototype.flat, а не Array.prototype.flatten? Прочтите наш разбор #SmooshGate, чтобы узнать!

Array.prototype.flatMap

Вот ещё один пример. У нас есть функция duplicate, которая принимает значение и возвращает массив, содержащий это значение дважды. Если применить duplicate к каждому значению массива, получится вложенный массив.

const duplicate = (x) => [x, x];

[2, 3, 4].map(duplicate);
// → [[2, 2], [3, 3], [4, 4]]

Затем можно вызвать flat для результата, чтобы свести массив:

[2, 3, 4].map(duplicate).flat(); // 🐌
// → [2, 2, 3, 3, 4, 4]

Поскольку этот шаблон очень распространён в функциональном программировании, для него теперь существует специальный метод flatMap.

[2, 3, 4].flatMap(duplicate); // 🚀
// → [2, 2, 3, 3, 4, 4]

flatMap немного эффективнее, чем выполнение map, за которым следует flat по отдельности.

Интересуетесь вариантами использования flatMap? Ознакомьтесь с объяснением Акселя Раушмайера.

Поддержка Array#{flat,flatMap}

Разделители цифр

· 2 мин. чтения
Маттиас Байненс ([@mathias](https://twitter.com/mathias))

Большие числовые литералы трудно быстро воспринимать человеческим глазом, особенно когда много повторяющихся цифр:

1000000000000
1019436871.42

Для улучшения читаемости новая языковая возможность JavaScript позволяет использовать подчеркивания в качестве разделителей в числовых литералах. Таким образом, приведенный выше пример можно переписать, группируя цифры по тысячам, например:

`String.prototype.matchAll`

· 3 мин. чтения
Матиас Биненс ([@mathias](https://twitter.com/mathias))

Часто нужно повторно применять одно и то же регулярное выражение к строке, чтобы получить все совпадения. В некоторой степени это уже возможно с помощью метода String#match.

В этом примере мы найдем все слова, состоящие только из шестнадцатеричных символов, а затем выведем каждое совпадение:

const string = 'Magic hex numbers: DEADBEEF CAFE';
const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
for (const match of string.match(regex)) {
console.log(match);
}

// Результат:
//
// 'DEADBEEF'
// 'CAFE'

Однако это дает только подстроки, которые соответствуют. Обычно требуется не только подстроки, но и дополнительная информация, такая как индекс каждой подстроки или группы захвата внутри каждого совпадения.

Уже сейчас можно достичь этого, написав собственный цикл и ведя учет объектов совпадений вручную, но это немного неудобно и не очень удобно:

const string = 'Magic hex numbers: DEADBEEF CAFE';
const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
let match;
while (match = regex.exec(string)) {
console.log(match);
}

// Результат:
//
// [ 'DEADBEEF', index: 19, input: 'Magic hex numbers: DEADBEEF CAFE' ]
// [ 'CAFE', index: 28, input: 'Magic hex numbers: DEADBEEF CAFE' ]

Новый API String#matchAll делает это проще, чем когда-либо: теперь вы можете написать простой цикл for-of, чтобы получить все объекты совпадений.

const string = 'Magic hex numbers: DEADBEEF CAFE';
const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
for (const match of string.matchAll(regex)) {
console.log(match);
}

// Результат:
//
// [ 'DEADBEEF', index: 19, input: 'Magic hex numbers: DEADBEEF CAFE' ]
// [ 'CAFE', index: 28, input: 'Magic hex numbers: DEADBEEF CAFE' ]

String#matchAll особенно полезен для регулярных выражений с группами захвата. Он предоставляет полную информацию для каждого отдельного совпадения, включая группы захвата.

const string = 'Favorite GitHub repos: tc39/ecma262 v8/v8.dev';
const regex = /\b(?<owner>[a-z0-9]+)\/(?<repo>[a-z0-9\.]+)\b/g;
for (const match of string.matchAll(regex)) {
console.log(`${match[0]} на ${match.index} в '${match.input}'`);
console.log(`→ владелец: ${match.groups.owner}`);
console.log(`→ репо: ${match.groups.repo}`);
}

Экспорт пространства имен модулей

· 1 мин. чтения
Маттиас Байненс ([@mathias](https://twitter.com/mathias))

В модулях JavaScript уже можно было использовать следующий синтаксис:

import * as utils from './utils.mjs';

Однако симметричный синтаксис export отсутствовал… до сегодняшнего дня:

export * as utils from './utils.mjs';

Это эквивалентно следующему:

import * as utils from './utils.mjs';
export { utils };

Публичные и приватные поля классов

· 4 мин. чтения
Матиас Биненс ([@mathias](https://twitter.com/mathias))

Несколько предложений расширяют существующий синтаксис классов JavaScript новыми функциями. В этой статье объясняется новый синтаксис публичных полей классов в V8 v7.2 и Chrome 72, а также предстоящий синтаксис приватных полей классов.

Вот пример кода, который создает экземпляр класса с именем IncreasingCounter:

const counter = new IncreasingCounter();
counter.value;
// выводит 'Получение текущего значения!'
// → 0
counter.increment();
counter.value;
// выводит 'Получение текущего значения!'
// → 1

Обратите внимание, что доступ к value выполняет некоторый код (например, записывает сообщение) перед возвратом результата. Теперь задайте себе вопрос, как вы бы реализовали этот класс на JavaScript? 🤔

Синтаксис классов ES2015

Вот как можно было бы реализовать IncreasingCounter с использованием синтаксиса классов ES2015:

class IncreasingCounter {
constructor() {
this._count = 0;
}
get value() {
console.log('Получение текущего значения!');
return this._count;
}
increment() {
this._count++;
}
}

Класс устанавливает геттер value и метод increment на прототипе. Более интересно то, что класс имеет конструктор, который создает свойство экземпляра _count и задает для него значение по умолчанию 0. В настоящее время мы часто используем префикс подчеркивания, чтобы указать, что _count не должен напрямую использоваться потребителями класса, но это всего лишь соглашение; на самом деле это не приватное свойство со специальной семантикой, обеспечиваемой языком.