Обновить
79
0
Денис Пушкарев@rock

JS

Отправить сообщение

Написать код с использованием ООП, которое превосходит возможности даже стандарта ES7? Звучит впечатляюще?

Да уж, во времена ES13 звучит впечатляюще.

Типа вебпака

Простой пример, попивший крови. Хотя мажорная версия на то и мажорная.

Ну пару if'ов можно сократить и до одного, вот только это сокращает время всего на пол наносекунды.

    if (this.length) {
      this.#head.next = element;
    } else {
      this.#tail = element;
    }

У вас в тесте используется Math.random() - очень тяжелая операция, так что большая часть времени у вас в тесте .push - время исполнения Math.random(). Если его заменить на счетчик, разница будет ещё значительней - примерно, двукратная. Почему - разбираться и копаться в сгенерированном коде лень. Может, особенности доступа к полям / модификации объектов.

Простая очередь:

class Queue {
  #tail = null;
  #head = null;
  length = 0;

  push(value) {
    const element = { next: null, value };
    if (this.#head) this.#head.next = element;
    if (this.#tail === null) this.#tail = element;
    this.#head = element;
    this.length++;
  }

  shift() {
    const element = this.#tail;
    if (element === null) return;
    this.#tail = element.next;
    if (this.#head === element) this.#head = null;
    this.length--;
    return element.value;
  }
}

Результаты:

// Массив:
scale | push, us | shift, us
    0 |       41 |        33
    1 |       22 |        15
    2 |       15 |        10
    3 |       11 |         6
    4 |        9 |         6
    5 |        8 |         9
    6 |        7 |        18
    7 |        7 |        29
    8 |        8 |        30
    9 |        9 |        30
   10 |        9 |        30
   11 |       10 |        31
   12 |        8 |        30
   13 |        8 |        30
   14 |       11 |       975
   15 |       12 |      2425
   16 |       18 |      5109
// Pow2Buffer:
scale | push, us | shift, us
    0 |       38 |        42
    1 |       22 |        16
    2 |       15 |        11
    3 |       13 |         6
    4 |       10 |         3
    5 |        9 |         2
    6 |        8 |         2
    7 |        8 |         2
    8 |        8 |         1
    9 |        9 |         1
   10 |        9 |         1
   11 |        9 |         1
   12 |        8 |         1
   13 |        9 |         1
   14 |        9 |         1
   15 |       10 |         1
   16 |        9 |         1
// Queue:
scale | push, us | shift, us
    0 |       57 |        31
    1 |       34 |        26
    2 |       22 |         9
    3 |       21 |         6
    4 |       15 |         5
    5 |       14 |         4
    6 |       14 |         4
    7 |       13 |         1
    8 |       13 |         1
    9 |       12 |         1
   10 |       12 |         1
   11 |       12 |         1
   12 |       12 |         1
   13 |       12 |         1
   14 |       13 |         2
   15 |       13 |         1
   16 |       16 |         1

Как-то так.

создание и последующая подчистка Garbage Collector'ом подобных избыточных объектов и полей - непозволительная роскошь

При этом, генератор по Symbol.iterator на каждый шаг итерации создает эти самые объекты и поля - хотя, возможно, движок это и оптимизирует.

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

Давайте разберем, что не так в ваших полифилах, на примере .map.

  1. Эти методы должны быть объявлены как неперечесляемые свойства.

Зачем? Есть такие странные люди, что обходят массивы при помощи цикла for-in:

var array = [1, 2, 3];
for (var key in array) console.log(array[key]);
// => 1
// => 2
// => 3
// => function myMap !!!

Подобные "полифилы" очень часто ломает чужой код. И это, пожалуй, самая популярная ошибка, при их написании.

  1. .map и прочие методы массива, добавленные в ES5, игнорируют дырки в массивах.

Array(5).map((_, i) => i); // => [empty × 5]
Array(5).myMap((_, i) => i); // => [0, 1, 2, 3, 4]
  1. Как выше упомянул @kahi4, длина массива должна запоминаться до начала итерации

const array = [1];
array.map(i => array.push(i)); // => [2]
array.myMap(i => array.push(i)); // => бесконечный цикл

Кроме того, в этот момент длина должна приводиться с помощью внутренней операции ToLength - но это уже мелочи жизни.

  1. С ES6, для поддержки субклассинга, .map использует @@species паттерн, его поддержка реализуется в несколько дополнительных строк.

class MyArray extends Array {}
MyArray.of(1, 2, 3).map(i => i) instanceof MyArray; // => true
MyArray.of(1, 2, 3).myMap(i => i) instanceof MyArray; // => false
  1. "this === null || this === window условие сработает в том случае, если метод вызывается как отдельная функция" - что это?

В спецификации прописаны 2 вещи:

  • this приводится к объекту с помощью операции ToObject - данная операция кидает ошибку на null и undefined.

  • Метод должен быть задан в строгом режиме.

По идее, никто не запрещает запускать .map на window:

[].map.call(window, it => it); // => [global]

А у вас, кроме этого, полифил не будет работать в окружении, отличном от браузера, где нет window - та же NodeJS.

Если ваш метод таки задается в строгом режиме, что явно не прописано ('use strict' - но может быть и контекст модуля), проблем у вас больше. Например, примитивы не будут приводиться к объектам:

[].map.call('1', (v, i, o) => typeof o); // => ['object']
[].myMap.call('1', (v, i, o) => typeof o); // => ['string']
  1. и далее. Обычно, полифилы данных методов, добавленных в ES5, рассчитаны на ES3 браузеры - и у них есть своя специфика.

Например, как было показано на примерах выше, данные методы - дженерики. Это значит, что они могут работать не только на массивах - но и на любых array-like сущностях - например, строках. Вот только в старых движках (IE8-, где строки не индексированы) без дополнительных костылей с этим проблемы - а где-то, например в старых версиях V8, есть соответствующие баги. И это только один момент специфики старых движков, что нужно учитывать.

  1. Полифил должен обнаруживать уже имеющуюся нативную фичу и использовать её, если есть такая возможность. Хотя бы так,

var myMap = [].map || function map() { /* ... */ };

но обычно все куда сложнее.

Пожалуйста, не пишите полифилы ES для использования в вашем проекте сами, если на все 100 не уверены, что без этого никак - обычно, можно найти уже готовые, проверенные, где кол-во ошибок сведено к минимуму.

Данные методы были добавлены в ES5 (IE9), ES6 разве что незначительно меняет их семантику - но тут даже о корректности по ES5 говорить не приходится -)

Хмм, а какие браузеры полностью поддерживают ES6? Я ни одного не знаю. Например, оптимизация хвостовой рекурсии (вернее, PTC) поддерживается сейчас только в Safari.

Эти компоненты добавили не позже классов, а вместе с ними (а что-то, вроде возможности установки прототипа существующего объекта, было задолго до них - де факто, но не в стандарте). И это делает классы синтаксическим сахаром, смотрим хотя бы на определение из педивикии

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

Справедливости ради, для ES6+ классы это исключительно синтаксический сахар. С помощью new.target (доступен и в обычных функциях), 3го аргумента Reflect.construct, Object.setPrototypeOf и WeakMap реализуются все недоступные в ES5 части логики классов.

Вы ошибаетесь чуть менее, чем во всем. Давайте рассмотрим только "итого":

__proto__ — это свойство любого объекта в JS, которое является ссылкой на свойство prototype функции-конструктора

Нет, это не свойство любого объекта в JS. Это legacy (сейчас в стандарте он опциональный - рекомендуется использовать Object.setPrototypeOf / Object.getPrototypeOf - про которые в посте и слова не сказано) accessor, расположенный на Object.prototype. Для объектов, не унаследованный от Object.prototype (например, Object.create(null)) его не будет. Оно может быть перекрыто собственным свойством объекта. Есть ещё __proto__ в литерале объекта, но это немного другое.

у каждой функции в JS есть свойство prototype, но только у функций!

Далеко не у каждой функции в JS есть свойство .prototype - оно есть только у конструкторов. Его нет у стрелочных функций, функций заданных синтаксисом методов ({ method() { /* ... */ } }), асинхронных функций, built-in функций и других.

Потомок связан с родителем свойством __proto__, которое указывает на свойство prototype родителя

Про __proto__ смотрите первый пункт. А про prototype - конструкторы не единственный способ наследования - на чей prototype будет указывать Object.create({}).__proto__?

И так глаза режет практически каждый абзац статьи.

Полифилы по usage у swc совсем не production-ready, что и не скрывают. Оптимизация по entry, с недавнего времени, работает вполне неплохо.

Подождите, вы безосновательно обвинили меня в "агитации и призыву к свержению власти" - и это утверждение висело, судя по дате добавления, полторы недели - и вместо того, что бы хотя бы извиниться - поставили минус моему комментарию? -)

Зашел посмотреть на их список - и внезапно обнаружил... себя.

Очевидно, что toxic-repos не проверяет данные хоть как-то.

Неплохо. Несколько дополнений:

Не рассмотрены такие относительно свежие вещи, как:

queueMicrotask
Promise.allSettled
Promise.any
Асинхронные итераторы / генераторы / for-await-of

Про await верхнего уровня небольшая ошибка - с недавних пор его можно использовать в контексте модуля.

Можно было бы расширить вещами, всё ещё не добавленными в стандарт языка, но активно применяемые сообществом, вроде Observable.

Прошел месяц - ни исправления, ни ответа...

не из caniuse-lite, а из собственной базы @babel/compat-data

@babel/compat-data собирается из compat-table.

Не совсем понял, что данной строчкой вы хотите показать - здесь подключается плагин babel-plugin-polyfill-corejs3, на стороне которого сейчас и находится вся логика, ответственная за подключение core-js и на стороне которого разбираются опции.

Простой пример - метод Array.prototype.at был добавлен в стандарт относительно недавно. esnext версия появилась в core-js@3.8, стабильная - в core-js@3.17. Видит Babel array.at(i), таргет IE - импорт чего ему вставлять? В core-js@3.0 нет ни es.array.at, ни esnext.array.at, попытка их импорта сломает код. Поэтому и пришлось добавить такой костыль.

Вообще, смотрим документацию.

Информация

В рейтинге
6 260-й
Откуда
Барнаул, Алтайский край, Россия
Дата рождения
Зарегистрирован
Активность