Нововведения в 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
— Функция которая производит элементы нового массива, принимает три аргумента:
currentValue
— Текущий обрабатываемый элемент массива.index
(необязательный) — Индекс обрабатываемого элемента в массиве.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)
biziwalker
04.02.2019 16:17Что-то не единого слова, что flat и flatMap включены в ES2019 github.com/tc39/proposals/blob/master/finished-proposals.md
Подправьте статью, данное предложение больше не является кандидатом (стадия 3)bad4iz Автор
04.02.2019 16:20+1да он перешел в stage 4 и в самом низу вашей ссылки
github.com/tc39/proposal-flatMap
rd_nino
04.02.2019 18:27Можно ли добавить в статью пример практического применения?
На ряде чисел.- не понятна суть этого улучшения. В чем польза?
symbix
05.02.2019 02:33Ну вот, один из моих любимых FizzBuzz-подобных вопросов испортили. :-)
С другой стороны, если кандидат знает такие новшества, это хороший признак, и всегда можно попросить написать без flat().
Chamie
05.02.2019 13:16А в чём похожесть на FizzBuzz? В FizzBuzz проблема в том, что хочется написать «красиво», а тут как раз легко написать «красиво».
symbix
05.02.2019 14:49В том, что цель одинаковая: отсеять за 3 минуты тех, кто вообще не умеет программировать.
Конечно же, принимается любое корректное решение. Рекурсия или стек, reduce или цикл ручками — не суть важно.
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])]);
FanAs
05.02.2019 15:51Не советую использовать spread синтаксис. Spread (...) добавит все значения в стек и передаст как список параметров, что при больших размерах массивов (10^6) сгенерирует stack overflow исключение.
Scf
Дождались, аллилуйя. После изучения скалы, мне всегда не хватало flatMap в яваскрипте.
sshikov
А вам что-то мешало его допилить? Он же сравнительно несложный внутри.
Scf
менять прототайп массива — не круто, чревато проблемами в будущем.
Zibx
Какими? Если не втаскивать кривых библиотек которые добавляют в прототип функции с аналогичными именами, то от расширения прототипов Function, Array, String, Number ничего страшного не произойдёт. Исключением является расширения прототипа Object — тут, действительно, не так может пойти очень многое.
Scf
Ну вот написал я свое расширение flatMap, а тут разработчики браузеров втащили кривую библиотеку с другой реализацией flatMap.
Сделать их идентичными не так просто из-за заранее неизвестных соглашений об обработке "необычных" типов.
justboris
Например тем, что вы напишите функцию flat для бесконечной вложенности, а в стандарт войдет реализация только для первого уровня вложенности, и весь ваш код сломается после обновления браузеров.
https://developers.google.com/web/updates/2018/03/smooshgate
Zibx
Так надо понимать что происходит. Если написать:
Array.prototype.flat = MyFlatFunction;
То после реализации и вхождения в стандарт — всё будет работать как и раньше, никакой код не сломается. Максимум — не будет ускоренной нативной реализации и отлетит сторонний код который положится на реализацию вошедшую в стандарт.
Но расширение прототипов — больно удобная фича чтоб её не использовать в своих проектах где целиком отвечаешь за весь код.
justboris
Вот именно это и плохо. Если у пользователя перестанет открываться сайт, ему неважно, ваш это был код, или сторонний.
Zibx
Если у пользователя перестанет открываться сайт, то такой код должен отлететь уже на этапе тестов, а в худшем случае — на стейджинг сервере. Я не призываю переопределять и даже небо и даже Аллаха, только утверждаю что это удобный функционал и на проекте где разработчики отвечают за всю кодовую базу — расширять действительно удобно.
justboris
Так не получится, потому что не новый код ломается, а уже существующий в проде по причине обновления браузера.
vanxant
В том и дело, что за код прототипов стандартных объектов вы отвечаете примерно никак. Ладно ещё добавление, типа полифила — можно сначала проверить, что текущий браузер не имеет нативной функции, и только в этом случае запускать свою. Значительно веселее, когда со временем разработчики что-то выпиливают целиком (типа isSameNode) или, ещё хуже, ограничивают область определения.
По мне так допустимо только два способа расширения прототипов: либо явные полифилы для старых браузеров, когда стандартная спецификация уже устаканилась, либо, ну если уж очень хочется своих красивостей, то только с myvendorprefix_ (типа mycool_flatMap).
sshikov
>После изучения скалы, мне всегда не хватало flatMap в яваскрипте.
Вообще-то, в вашем оригинальном пожелании нет ни слова о прототипе :)