Нововведения в ECMAScript



Здравствуйте читатели Хабр. Предлагаю вашему вниманию интересную штуку которая в скором времени появится уже появилась в стандарте ECMAScript. Она уже доступна почти во всех браузерах и весь функционал тоже. Это нововведение всего лишь сахар-обертка и ее можно решить своими средствами и это я вам тоже покажу. Но этот сахар позволит нам писать кода меньше, ведь мы это так любим. Итак, добавляется базовые функции к массиву, в данном случае их 2, которые позволят нам разворачивать многоуровневые массивы в один или более уровней вложенности. Да да и все это как говориться из коробки


ECMAScript Proposal: Array.prototype.{flat,flatMap}


Этот Proposal когда то был известен под именем Array.prototype.flat{ten, Map}. Он имеет статус stage 3 4 из TC39 то есть уже Candidate Finished. Я думаю стоит немного отвлечься и рассказать о этом самом TC39 и о стандартах, кто знаком могут пропустить сие отступление. итак:


Стандарты JavaScript и как его готовят.


ECMAScript


ECMAScript — это стандарт, который развивается и поддерживается ассоциацией ECMA International. Он был принят Генеральной Ассамблеей Ecma в июне 1997. А если точнее, ECMAScript — это стандарт ECMA-262, который называется «Спецификация языка ECMAScript». Стандарт создавался на основе оригинальной версии JavaScript от Брендана Айха из Netscape и интерпретатора JScript от Microsoft, но с тех пор значительно развился.


JavaScript — это реализация спецификации ECMAScript. Это означает, что по мере того, как у спецификации появляются новые черновики или опубликованные редакции, разработчики браузеров и фреймворков вроде Node.js должны последовательно внедрять новый функционал. Для этого вносятся изменения в движки, которые эти браузеры и фреймворки используют для интерпретирования и выполнения кода JavaScript.


Зачем нужна спецификация? В различных браузерах используются различные движки JavaScript, например, V8 в Chrome, SpiderMonkey в Firefox, и так далее. Когда вы пишете на JavaScript, вы ожидаете, что все движки во всех окружениях будут разбирать и выполнять ваш код абсолютно одинаково. Без стандартизованной спецификации любой из этих движков был бы волен исполнять JavaScript как ему вздумается, очевидно, что это не очень хорошо.


TC39


Ecma International Technical Committee 39 (он же TC39) — комитет очень умных людей, он связан с группой участников тоже кстати умные. Они все дружно собираются вместе на встречах комитета приблизительно раз в два месяца и обсуждают заранее подготовленные списки вопросов.
Задачей TC39 является поддержка и обновление упомянутой ранее спецификации ECMAScript, после обсуждения и всеобщего согласия. Сюда относятся синтаксис языка, семантика, библиотеки и сопутствующие технологии, на которых основывается язык.


Этот процесс определён TC39. Всё начинается с черновика функционала, расширяющего или изменяющего спецификацию. Такие черновики часто исходят от сообществ разработчиков, или же от самих участников TC39. Если предложение оказывается достойным, черновик получает статус официального предложения.


Затем на своём пути от идеи до публикации предложение проходит несколько определенных стадий. Они пронумерованы от нуля до четырёх мыж программисты:


[“Strawman”, “Proposal”, “Draft”, “Candidat”, “Finished”]

Переход на любую следующую стадию требует одобрения TC39. Если предложение добралось до стадии 4, можно ожидать, что его включат в следующую официально опубликованную редакцию спецификации стандарта ECMA-262, и в итоге оно появится в окружении, которое выполняет JavaScript. Больше об этом процессе вы можете узнать из самого документа.


Все существующие предложения на введения в стандарт можно посмотреть тут на github
ну теперь поехали дальше ...


Array.prototype.flat


Метод flat() возвращает новый массив,


var newArray = arr.flat(depth);

в котором все элементы вложенных под массивов были рекурсивно "подняты" на указанный уровень depth.


Пример:


const arr1 = [1, 2, [3, 4]];
arr1.flat(); //? [1, 2, 3, 4]

const arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat(); //? [1, 2, 3, 4, [5, 6]]

const arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2); //? [1, 2, 3, 4, 5, 6]

Также существует и побочное действие, он удаляет не определенные элементы массива


Пример:


const arr4 = [1, 2, , 4, 5];
arr4.flat(); //? [1, 2, 4, 5]

Рубрика делаем сами


Используем reduce и concat


Пример:


const arr1 = [1,2,3,[1,2,3,4, [2,3,4]]];

function flattenDeep(arr1) {
   return arr1.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []);
}
flattenDeep(arr1); //? [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]

Array.prototype.flatMap



Метод flatMap() сначала применяет функцию к каждому элементу, а затем преобразует полученный результат в плоскую структуру и помещает в новый массив. Это идентично map функции, с последующим применением функции flat с параметром depth ( глубина ) равным 1, но flatMap часто бывает полезным, так как работает немного более эффективно.


var new_array = arr.flatMap(function callback(currentValue[, index[, array]]) {
    // return element for new_array
}[, thisArg])

callback — Функция которая производит элементы нового массива, принимает три аргумента:


  1. currentValue — Текущий обрабатываемый элемент массива.
  2. index (необязательный) — Индекс обрабатываемого элемента в массиве.
  3. array (необязательный) — Массив по которому осуществляется обход.
    thisArg (необязательный) — Значение используемое в качестве this при вызове функции callback.
    Возвращаемое значение — Новый массив, каждый элемент которого является результатом выполнения функции callback и "поднят" на уровень 1.

пример:


[
    { x: 1, y: 2 }, 
    { x: 3, y: 4 }, 
    { x: 5, y: 6 }
].flatMap(c => [c.x, c.y])  //?  [1, 2, 3, 4, 5, 6]

// or

const orders = [
    {
        orderId: 42,
        items: [
            {name: 'soap', price: 1.99},
            {name: 'shampoo', price: 4.99}
        ]
    },
    {
        orderId: 1337,
        items: [
            {name: 'toothpaste', price: 5.59},
            {name: 'toothbrush', price: 8.99}
        ]
    }
];

orders.flatMap(i => i.items.map(i => i.name)); //? ["soap", "shampoo", "toothpaste", "toothbrush"]

Рубрика делаем сами


var arr1 = [1, 2, 3, 4];

arr1.reduce((acc, x) => acc.concat([x * 2]), []);
// [2, 4, 6, 8]

Заключение


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


ps: пока писалась статья :), Proposal перешел в stage 4
Array.prototype.{flat,flatMap} to stage 4, per 2019.01.29 TC39

Комментарии (29)


  1. Scf
    04.02.2019 16:00
    +2

    Дождались, аллилуйя. После изучения скалы, мне всегда не хватало flatMap в яваскрипте.


    1. sshikov
      04.02.2019 16:09

      А вам что-то мешало его допилить? Он же сравнительно несложный внутри.


      1. Scf
        04.02.2019 16:18
        +1

        менять прототайп массива — не круто, чревато проблемами в будущем.


        1. Zibx
          04.02.2019 18:49

          Какими? Если не втаскивать кривых библиотек которые добавляют в прототип функции с аналогичными именами, то от расширения прототипов Function, Array, String, Number ничего страшного не произойдёт. Исключением является расширения прототипа Object — тут, действительно, не так может пойти очень многое.


          1. Scf
            04.02.2019 18:54

            Ну вот написал я свое расширение flatMap, а тут разработчики браузеров втащили кривую библиотеку с другой реализацией flatMap.


            Сделать их идентичными не так просто из-за заранее неизвестных соглашений об обработке "необычных" типов.


          1. justboris
            04.02.2019 19:15
            +1

            Какими?

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


            https://developers.google.com/web/updates/2018/03/smooshgate


            1. Zibx
              04.02.2019 23:27

              Так надо понимать что происходит. Если написать:
              Array.prototype.flat = MyFlatFunction;

              То после реализации и вхождения в стандарт — всё будет работать как и раньше, никакой код не сломается. Максимум — не будет ускоренной нативной реализации и отлетит сторонний код который положится на реализацию вошедшую в стандарт.
              Но расширение прототипов — больно удобная фича чтоб её не использовать в своих проектах где целиком отвечаешь за весь код.


              1. justboris
                04.02.2019 23:45

                отлетит сторонний код который положится на реализацию вошедшую в стандарт.

                Вот именно это и плохо. Если у пользователя перестанет открываться сайт, ему неважно, ваш это был код, или сторонний.


                1. Zibx
                  05.02.2019 02:57

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


                  1. justboris
                    05.02.2019 03:52

                    Если у пользователя перестанет открываться сайт, то такой код должен отлететь уже на этапе тестов

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


              1. vanxant
                05.02.2019 00:09
                +1

                В том и дело, что за код прототипов стандартных объектов вы отвечаете примерно никак. Ладно ещё добавление, типа полифила — можно сначала проверить, что текущий браузер не имеет нативной функции, и только в этом случае запускать свою. Значительно веселее, когда со временем разработчики что-то выпиливают целиком (типа isSameNode) или, ещё хуже, ограничивают область определения.
                По мне так допустимо только два способа расширения прототипов: либо явные полифилы для старых браузеров, когда стандартная спецификация уже устаканилась, либо, ну если уж очень хочется своих красивостей, то только с myvendorprefix_ (типа mycool_flatMap).


        1. sshikov
          04.02.2019 20:17
          -1

          >После изучения скалы, мне всегда не хватало flatMap в яваскрипте.
          Вообще-то, в вашем оригинальном пожелании нет ни слова о прототипе :)


  1. greabock
    04.02.2019 16:05

    лодаш рип


  1. biziwalker
    04.02.2019 16:17

    Что-то не единого слова, что flat и flatMap включены в ES2019 github.com/tc39/proposals/blob/master/finished-proposals.md

    Подправьте статью, данное предложение больше не является кандидатом (стадия 3)


    1. bad4iz Автор
      04.02.2019 16:20
      +1

      да он перешел в stage 4 и в самом низу вашей ссылки
      github.com/tc39/proposal-flatMap


  1. bad4iz Автор
    04.02.2019 16:46
    +2

    `Array.prototype.{flat,flatMap}` to stage 4, per 2019.01.29 TC39


  1. rd_nino
    04.02.2019 18:27

    Можно ли добавить в статью пример практического применения?
    На ряде чисел.- не понятна суть этого улучшения. В чем польза?


    1. Scf
      04.02.2019 18:58
      +3

      const persons = [...];
      const maleAges = persons.flatMap(p => {
        if (p.male) return [p.age]; else return [];
      })


      1. rd_nino
        04.02.2019 19:03

        Спасибо! Теперь все ясно. :)


  1. symbix
    05.02.2019 02:33

    Ну вот, один из моих любимых FizzBuzz-подобных вопросов испортили. :-)


    С другой стороны, если кандидат знает такие новшества, это хороший признак, и всегда можно попросить написать без flat().


    1. Chamie
      05.02.2019 13:16

      А в чём похожесть на FizzBuzz? В FizzBuzz проблема в том, что хочется написать «красиво», а тут как раз легко написать «красиво».


      1. symbix
        05.02.2019 14:49

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


        Конечно же, принимается любое корректное решение. Рекурсия или стек, reduce или цикл ручками — не суть важно.


  1. Chamie
    05.02.2019 13:12

    Самого короткого варианта замены не написали:

    const ar = [[1,2],[3,4],[5,6]];
    ar.reduce((a,b)=>[...a,...b]);
    //[1,2,3,4,5,6]
    Это если вложенные элементы — гарантированно массивы, конечно. Если нет, то
    ar.reduce((a, b) => [...a, ...(Array.isArray(b) ? b : [b])]);


    1. justboris
      05.02.2019 13:28

      Еще короче


      [].concat(...arr)


      1. Chamie
        05.02.2019 14:39

        Вообще шикарно, спасибо. Жаль только, не чейнится — в середину цепочки преобразований не вставить, только в начало.


    1. FanAs
      05.02.2019 15:51

      Не советую использовать spread синтаксис. Spread (...) добавит все значения в стек и передаст как список параметров, что при больших размерах массивов (10^6) сгенерирует stack overflow исключение.


      1. Chamie
        05.02.2019 22:06

        В каких единицах вы указали размер? И на каком объёме ОЗУ тестировать?

        Скрытый текст
        1'000'000:

        10'000'000:
        ФФ:

        Хром:


        1. justboris
          05.02.2019 23:19

          Видимо, имелось в виду мое решение: `[].concat(...arr)`.

          Оно действительно тормозит на массиве с миллионом элементов.


          1. Chamie
            06.02.2019 11:58

            И правда.

            Падает даже на одном миллионе
            FF:

            Chrome: