What's New in JavaScript

Node.js JavaScript

            What's New in JavaScript

Команда V8 в Google IO поделилась с нами несколько дней назад.«Что нового в JavaScript»Тема, скорость обмена очень низкая, всем рекомендую прослушать в качестве упражнения на аудирование. Прочитав его, я составил текстовую версию, чтобы помочь вам быстро понять общий контент.Гости в основном разделяли следующие моменты:

  1. Парсинг JS в 2 раза быстрее
  2. async выполняется в 11 раз быстрее
  3. Уменьшено использование памяти в среднем на 20%
  4. Поля класса могут инициализировать переменные прямо в классе, не записывая их в конструктор.
  5. префикс частной переменной
  6. string.matchAll неоднократно использовался для регулярного сопоставления
  7. числовой разделитель позволяет нам использовать _ в качестве разделителя для улучшения читаемости при написании чисел
  8. bigint поддержка нового типа больших чисел
  9. Intl.NumberFormat отображение локализованного форматированного числа
  10. Array.prototype.flat(), Array.prototype.flatMap() Метод выравнивания многослойного массива
  11. Object.entries() и Object.fromEntries() для быстрых операций над массивами объектов.
  12. globalThis глобальная эта поддержка без зависимостей от среды
  13. Стабильный вывод отсортированного результата Array.prototype.sort()
  14. Intl.RelativeTimeFormat(), Intl.DateTimeFormat() локализованное время отображения
  15. Intl.ListFormat() локализован для отображения нескольких списков существительных
  16. Intl.locale() предоставляет различные постоянные запросы для локализованного языка.
  17. Ожидание верхнего уровня без написания асинхронной поддержки
  18. Добавление Promise.allSettled() и Promise.any() расширяет возможности сценариев Promise.
  19. Тип WeakRef используется для создания слабых ссылок на частичные переменные для уменьшения утечек памяти.

Async-исполнение в 11 раз быстрее, чем раньше

Используйте его в начале11x fasterЦифры удивили всех, и многие студенты недоумевали, как им это удалось. На самом деле эта оптимизация в последнее время не делается.Команда V8 опубликовала статью в ноябре прошлого года.Более быстрые асинхронные функции и обещания, вот очень подробное описание того, как оптимизировать async/await под эту скорость, что в основном связано со следующими тремя моментами:

  • TurboFan: новый компилятор JS
  • Orinoco: Новый двигатель GC
  • Ошибка ожидания Node.js на 8

Chrome родился в 2008 году, а Chrome представил компилятор Crankshaft спустя 10 лет. Спустя годы этот ветеран уже не может удовлетворить существующие потребности в оптимизации. Ведь автор в то время не ожидал, что мир фронтенда будет развиваться так быстро . О том, почему коленчатый вал заменили на TurboFan, можно прочитать здесь.«Запуск зажигания и TurboFan», в исходном тексте говорится:

Crankshaft поддерживает только часть функций, оптимизирующих JavaScript. Он не проектирует код через структурированную обработку исключений, т. е. блоки кода не разделяются ключевыми словами, такими как try, catch, finally и т. д. Кроме того, поскольку Cranksshaft придется создавать девять наборов различных кодов фреймворка для каждой новой функции, чтобы адаптировать ее к различным платформам, также сложно адаптироваться к новым функциям языка Javascript. Кроме того, структура кода Crankshaft также ограничивает расширение оптимизированного машинного кода. Хотя команда разработчиков двигателя V8 поддерживает более 10 000 строк кода для каждой архитектуры чипа, Crankshaft лишь немного выжимает из Javascript производительность. через:«Как работает Javascript: зажигание ядра и турбовентилятор двигателя V8»

TurboFan обеспечивает лучшую архитектуру и может добавлять новые функции оптимизации без изменения архитектуры, что обеспечивает хорошую архитектурную поддержку для оптимизации функций языка JavaScript в будущем, позволяя команде тратить меньше времени на обработку функций и кодирование различных платформ. Из сравнения данных исходного текста видно, что оптимизация примерно в 8 раз только за счет смены компилятора... Преклонение колен перед большими парнями V8.

Новый движок Orinoco GC использует отдельный поток для асинхронной обработки, поэтому он не влияет на выполнение основного потока JS. Что касается ошибки async/await, упомянутой в конце, она заставила команду разработчиков V8 задуматься о сокращении исходной реализации async/await, основанной на 3 обещаниях, до 2 и, наконец, до 1! Наконец, в результате написание async/await выполняется быстрее, чем написание Promise напрямую.

мы знаемawaitЗа ним следует объект Promise, но даже если это не Promise, JS поможет нам обернуть его в Promise. В движке V8 для реализации этой упаковки требуется как минимум один промис и два микрозадачных процесса. Это немного неэкономично, когда это уже само обещание. Чтобы реализовать async/await, после завершения ожидания контекст исходной функции должен быть сброшен и предоставленawaitВ результате определение спецификации в настоящее время также нуждается в промисе, что, по мнению команды V8, не нужно, поэтому они предлагают спецификацию.удалить эту функцию.

Наконец, чиновник также рекомендует нам: использовать async/await вместо написанного от руки кода Promise и использовать обещание, предоставленное движком JavaScript, вместо того, чтобы реализовывать его самостоятельно.

Numeric Seperator

С появлением Babel в JS стало меньше синтаксического сахара, и числовой разделитель — один из них. Короче говоря, он обеспечивает поддержку разделителей, когда мы записываем числа от руки, что позволяет нам писать большие числа с удобочитаемостью.

На самом деле, это очень простой синтаксический сахар, поэтому я перечисляю его отдельно, главным образом потому, что он просто решает проблему моей предыдущей реализации. У меня есть требование к набору данных статьи, и я хочу вставить рекламу в соответствии с правилами, заданными продуктом. Как показано на рисунке, некрасная коробка — это статья, а красная — реклама. Поскольку правила вставки будут часто меняться в зависимости от потребностей (сердца) продукта, мы изначально использовали две переменные для маркировки:

const news = [1, 3, 5, 6, 7, 9, 10, 11];
const ads = [2, 4, 8, 12];

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

+---+---+---+
| 0 | 1 | 0 |
+---+---+---+
| 1 | 0 | 0 |
+---+---+---+
| 0 | 1 | 0 |
+---+---+---+
| 0 | 0 | 1 |
+---+---+---+

1 011 010 100 010 001
// 首位为常量 1
// 2-4 位记录一行多少条
// 后续按照新闻和广告的位置进行记录

Наконец, мы используем переменную0b1011010100010001На этом запись двух типов информации завершена. Таким образом, много данных объединяется вместе, чтобы решить наши предыдущие проблемы, но это приносит новые проблемы. Вы также можете видеть, что, если вы разделите комментарии по пробелам, вы сможете лучше их понять. После удаления это вызвало большие трудности в чтении. И синтаксический сахар разделителя чисел может решить эту проблему.0b1_011_010_100_010_001Так намного лучше читать.

Promise

Хотя async/await подходит для большинства сценариев, извините, Promise незаменим в некоторых сценариях.Promsie.all()а такжеPromise.race()Это особое существование. а такжеPromise.allSettled()а такжеPromise.any()Это недавно добавленный метод.По сравнению со своими предшественниками, эти два имеют характеристику игнорирования ошибок для достижения своих целей.

Раньше у нас было требование безопасного хранения файлов в службе хранилища.Для аварийного восстановления у нас фактически есть два S3, один HBase и локальное хранилище. Итак, каждый раз, когда вам нужна логика, подобная следующей:

for(const service of services) {
  const result = await service.upload(file);
  if(result) break;
}

Но на самом деле мне плевать на ошибки, моя цель сделать так, чтобы в итоге был сервис, который можно успешно выполнить, поэтомуPromise.any()На самом деле эту проблему можно решить.

await Promise.any( services.map(service => service.upload(file)) );

Promise.allsettled()а такжеPromise.any()Введение Promise расширяет возможности. Возможно, в будущем будут добавлены дополнительные функции, такие какPromise.try(), Promise.some(), Promise.reduce() ...

WeakRef

Я сначала не совсем понял новый тип WeakRef, ведь всегда чувствовал, что Chrome подрос и точно позаботится о своем мусоре. Однако все не так просто, как я думал, мы знаем, что сборка мусора JS в основном имеет два метода: «удаление меток» и «подсчет ссылок». Счетчик ссылок заключается в том, что, пока на переменную ссылаются еще раз, счетчик увеличивается на 1, а когда на одну ссылку меньше, счетчик уменьшается на 1. Когда ссылка равна 0, это означает, что у вас нет значения использования. , иди на помойку!

До WeakRef было фактически два похожих типа, WeakMap и WeakSet. Возьмем, к примеру, WeakMap, в нем оговаривается, что его ключ должен быть объектом, а все его объекты являются слабыми ссылками. Например:

//map.js
function usageSize() {
  const used = process.memoryUsage().heapUsed;
  return Math.round(used / 1024 / 1024 * 100) / 100 + 'M';
}

global.gc();
console.log(usageSize()); // ≈ 3.23M

let arr = new Array(5 * 1024 * 1024);
const map = new Map();

map.set(arr, 1);
global.gc();
console.log(usageSize()); // ≈ 43.22M

arr = null;
global.gc();
console.log(usageSize()); // ≈ 43.23M
//weakmap.js
function usageSize() {
  const used = process.memoryUsage().heapUsed;
  return Math.round(used / 1024 / 1024 * 100) / 100 + 'M';
}

global.gc();
console.log(usageSize()); // ≈ 3.23M

let arr = new Array(5 * 1024 * 1024);
const map = new WeakMap();

map.set(arr, 1);
global.gc();
console.log(usageSize()); // ≈ 43.22M

arr = null;
global.gc();
console.log(usageSize()); // ≈ 3.23M

Выполнить отдельноnode --expose-gc map.jsа такжеnode --expose-gc weakmap.jsВы можете заметить разницу. Строгие ссылки на массивы сохраняются как в arr, так и в Map, поэтому простая очистка памяти переменной arr в Map не освобождает ее, потому что у Map все еще есть счетчик ссылок. В WeakMap его ключ является слабой ссылкой и не учитывается в подсчете ссылок, поэтому при очистке arr массив будет восстановлен, поскольку счетчик ссылок равен 0.

Как сказано в совместном использовании, WeakMap и WeakSet достаточно хороши, но требуют, чтобы ключи были объектами, что в некоторых сценариях не очень опробовано. Поэтому они выставляют более удобный тип WeakRef. Типы WeakRef также существуют в Python, которые делают похожие вещи. На самом деле мы в основном отмечаем, что ссылка WeakRef не считается по ссылке, что легко понять. НапримерMDNПроблема циклической ссылки, которую счетчик ссылок не может очистить, упоминается в:

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o

  return "azerty";
}

f();

Если вы попытаетесь переписать его с помощью WeakRef, поскольку WeakRef не вычисляет счетчик ссылок, счетчик всегда равен 0, и он будет нормально переработан при следующем перезапуске.

function f() {
  var o = new WeakRef({});
  var o2 = o;
  o.a = o2;

  return "azerty";
}

f();

В предыдущем требовании к нескольким процессам нам нужно отправить данные из дочернего процесса в основной процесс.Способ, который мы используем, записывается следующим образом:

const metric = 'event';
global.DATA[metric] = {};

process.on(metric, () => {
  const data = global.DATA[metric];
  delete global.DATA[metric];
  return data;
});

Код выглядит странно, потому чтоglobal.DATA[metric]как сильная ссылка, если непосредственно в случаеreturn global.DATA[metric]Если есть счетчик ссылок, эта глобальная переменная всегда будет занимать память. На этом этапе, если вы используете WeakRef для перезаписи, вы можете уменьшитьdeleteлогика.

const metric = 'event';
global.DATA[metric] = new WeakRef({});

process.on(metric, () => {
  const ref = global.DATA[metric];
  if(ref !== undefined) {
    return ref.deref();
  }
  return ref;
});

постскриптум

В дополнение к нескольким функциям, которые я упомянул выше, есть много других функций, которые также очень хороши для гребной лодки. НапримерString.matchAll()Давайте больше не будем писать, когда мы делаем несколько совпадений! Поддержка классов локализации Intl позволяет нам раньше отказаться от moment.js, особенноRelativeTimeFormatКласс действительно высвобождает нашу производительность, но текущая конфигурация интерфейса кажется более индивидуальной, я не знаю, какой будет последующая мелкозернистая поддержка спроса.

Использованная литература: