![image](https://habrastorage.org/getpro/habr/post_images/135/19e/d15/13519ed155eca53f0f88f3d4a32befa3.jpg)
1. Обязательные параметры функции
ES6 позволяет задавать значения формальных параметров по умолчанию, что позволяет, при вызове функции без указания значений этих параметров, подставлять их стандартные значения. Это позволяет задавать параметры, без передачи которых функция работать не будет.
В следующем примере мы задаём функцию
required()
как значение по умолчанию для параметров a
и b
. Это означает, что если a
или b
не будут переданы функции при вызове, будет вызвана функция required()
и мы получим сообщение об ошибке.const required = () => {throw new Error('Missing parameter')};
//При вызове этой функции произойдёт ошибка, если параметры "a" или "b" не заданы
const add = (a = required(), b = required()) => a + b;
add(1, 2) //3
add(1) // Error: Missing parameter.
2. Секреты метода reduce
Метод reduce объекта
Array
чрезвычайно универсален. Обычно его используют для преобразования массива неких элементов к единственному значению. Однако с его помощью можно сделать ещё очень много всего полезного.Обратите внимание на то, что в следующих примерах мы полагаемся на то, что исходное значение переменной — это либо массив, либо объект, а не нечто вроде строки или числа.
?2.1. Использование reduce для одновременного выполнения мэппинга и фильтрации массива
Представим, что перед нами стоит следующая задача. Имеется список элементов, каждый из которых надо модифицировать (что сводится к использованию метода map), после чего отобрать из него несколько элементов (это можно сделать с помощью метода filter). Эта задача вполне решаема последовательным применением методов
map
и filter
, но так по списку элементов придётся пройтись дважды. А нас это не устраивает.В следующем примере нужно удвоить значение каждого элемента в массиве, после чего — отобрать лишь те из них, которые больше 50. Обратите внимание на то, как мы можем использовать мощный метод
reduce
и для удвоения, и для фильтрации элементов. Это очень эффективно.const numbers = [10, 20, 30, 40];
const doubledOver50 = numbers.reduce((finalList, num) => {
num = num * 2; //удвоить каждое число (аналог map)
//отобрать числа > 50 (аналог filter)
if (num > 50) {
finalList.push(num);
}
return finalList;
}, []);
doubledOver50; // [60, 80]
?2.2. Использование reduce вместо map или filter
Если вы проанализировали вышеприведённый пример, то для вас окажется совершенно очевидной возможность использования метода
reduce
вместо map
или filter
.?2.3. Использование reduce для анализа расстановки скобок
Вот ещё один пример полезных возможностей метода
reduce
.Предположим, что у нас имеется строка со скобками, и нам нужно узнать, сбалансированы они, то есть, во-первых, равно ли количество открывающих скобок скобкам закрывающим, и, во-вторых, то, что соответствующие открывающие скобки находятся перед закрывающими.
Эту задачу можно решить с помощью метода
reduce
так, как показано ниже. Тут мы используем переменную counter
с начальным значением, равным 0. Мы увеличиваем её значение на единицу, если находим символ «(», и уменьшаем, если находим символ «)». Если скобки в строке сбалансированы, на выходе должен получиться 0.//Возвращает 0 если скобки сбалансированы.
const isParensBalanced = (str) => {
return str.split('').reduce((counter, char) => {
if(counter < 0) { //matched ")" before "("
return counter;
} else if(char === '(') {
return ++counter;
} else if(char === ')') {
return --counter;
} else { //найден какой-то другой символ
return counter;
}
}, 0); //<-- начальное значение для счётчика
}
isParensBalanced('(())') // 0 <-- скобки сбалансированы
isParensBalanced('(asdfds)') //0 <-- скобки сбалансированы
isParensBalanced('(()') // 1 <-- скобки несбалансированы
isParensBalanced(')(') // -1 <-- скобки несбалансированы
?2.4. Подсчёт количества совпадающих значений массива (преобразование массива в объект)
Иногда нужно подсчитать одинаковые элементы массива или преобразовать массив в объект. Для решения этих задач также можно использовать
reduce
.В следующем примере мы хотим подсчитать количество автомобилей каждого типа и поместить результаты этих вычислений в объект.
var cars = ['BMW','Benz', 'Benz', 'Tesla', 'BMW', 'Toyota'];
var carsObj = cars.reduce(function (obj, name) {
obj[name] = obj[name] ? ++obj[name] : 1;
return obj;
}, {});
carsObj; // => { BMW: 2, Benz: 2, Tesla: 1, Toyota: 1 }
На самом деле, возможности
reduce
на этом не ограничиваются. Если вам интересны подробности об этом полезном методе — изучите примеры с этой страницы.3. Деструктурирование объектов
?3.1. Удаление ненужных свойств
Бывает так, что из объекта требуется удалить ненужные свойства. Возможно, это свойства, которые содержат секретные сведения, возможно — они слишком велики. Вместо того, чтобы перебирать весь объект и удалять подобные свойства, можно извлечь эти свойства в переменные и оставить нужные свойства в rest-параметре.
В следующем примере нам нужно избавиться от свойств
_internal
и tooBig
. Их можно присвоить переменным с такими же именами и сохранить оставшиеся свойства в rest-параметре cleanObject
, который можно использовать позже.let {_internal, tooBig, ...cleanObject} = {el1: '1', _internal:"secret", tooBig:{}, el2: '2', el3: '3'};
console.log(cleanObject); // {el1: '1', el2: '2', el3: '3'}
?3.2. Деструктурирование вложенных объектов в параметрах функции
В следующем примере свойство
engine
— это вложенный объект объекта car
. Если нам нужно узнать, скажем, свойство vin
объекта engine
, его легко можно деструктурировать.var car = {
model: 'bmw 2018',
engine: {
v6: true,
turbo: true,
vin: 12345
}
}
const modelAndVIN = ({model, engine: {vin}}) => {
console.log(`model: ${model} vin: ${vin}`);
}
modelAndVIN(car); // => model: bmw 2018 vin: 12345
?3.3. Слияние объектов
ES6 поддерживает оператор расширения (spread), выглядящий как три точки. Обычно его применяют для работы с массивами, но его можно использовать и с обычными объектами.
В следующем примере мы используем оператор расширения для формирования нового объекта из двух существующих. Ключи свойств объекта №2 переопределят ключи свойств объекта №1. В частности, свойства
b
и c
из object2
переопределят такие же свойства объекта object1
.let object1 = { a:1, b:2,c:3 }
let object2 = { b:30, c:40, d:50}
let merged = {…object1, …object2} //применим оператор расширения для формирования нового объекта
console.log(merged) // {a:1, b:30, c:40, d:50}
4. Коллекции
?4.1. Удаление повторяющихся значений из массивов
В ES6 можно избавляться от повторяющихся значений, используя коллекции (Set). Коллекции могут содержать только уникальные значения.
let arr = [1, 1, 2, 2, 3, 3];
let deduped = [...new Set(arr)] // [1, 2, 3]
?4.2. Использование методов массивов с коллекциями
Преобразование коллекций в массивы не сложнее, чем использование вышеупомянутого оператора расширения. Все методы массивов можно использовать и с коллекциями.
Предположим, у нас имеется коллекция, которую нужно отфильтровать так, чтобы в ней содержались только элементы, которые больше 3. Вот как это сделать.
let mySet = new Set([1,2, 3, 4, 5]);
var filtered = [...mySet].filter((x) => x > 3) // [4, 5]
5. Деструктурирование массивов
Часто бывает так, что функция возвращает множество значений в виде массива. Извлечь эти значения из массива можно с использованием техники деструктурирования.
?5.1. Обмен значений переменных
Вот пример того, как новые средства работы с массивами позволяют организовать обмен значений двух переменных.
let param1 = 1;
let param2 = 2;
//поменяем местами значения param1 и param2
[param1, param2] = [param2, param1];
console.log(param1) // 2
console.log(param2) // 1
?5.2. Приём и обработка нескольких значений, возвращаемых функцией
В следующем примере мы загружаем некую статью, находящуюся по адресу
/post
, и связанные с ней комментарии с адреса /comments
. Тут применяется конструкция async/await
, функция возвращает результат в виде массива.Деструктурирование упрощает присвоение результатов работы функции соответствующим переменным.
async function getFullPost(){
return await Promise.all([
fetch('/post'),
fetch('/comments')
]);
}
const [post, comments] = getFullPost();
Итоги
В этом материале мы рассмотрели несколько простых, но неочевидных приёмов использования новых возможностей ES6. Надеемся, они вам пригодятся.
Уважаемые читатели! Если вы знаете какие-нибудь интересные приёмы использования возможностей ES6 — просим ими поделиться.
![](https://habrastorage.org/files/1ba/550/d25/1ba550d25e8846ce8805de564da6aa63.png)
Комментарии (68)
vlreshet
27.03.2018 17:35Фишка с required аргументами понравилась, это та вещь которой по непонятной причине нет «из коробки»
staticlab
27.03.2018 17:48Если у вас транспилируется stage 2, то можно писать и так:
const requiredArgument = new Error('Missing argument') const add = (a = throw new Error('Argument `a` is required'), b = throw new Error('Argument `b` is required')) => a + b;
vitaliy2
27.03.2018 20:17Вообще, проверка как в статье бесполезна, т.?к. чаще всего, если делается проверка аргумента, то будет сделана и проверка типа, и даже, возможно, диапазона значений. Здесь же только проверка, что не undefined, да и со стандартной ошибкой, что как-то скупо…
Но если бы можно было обозначать примерно в таком стиле, то мб и пошло:
function(arg!, otherArg!) {}
Тоже только на undefined, но короче и с нормальной ошибкой.
Aquahawk
27.03.2018 18:03-1var filtered = [...mySet].filter((x) => x > 3) // [4, 5]
напишите тестик на производительность этого на сете хотяб на пару тысяч элементов, а лучше больше, по сравнению с foreach по этому сету где сложат всё нужное в отдельный заранее созданный массив. И прогоните на современных браузерах включая мобилки. Я прям вот специально сейчас не пойду и не буду писать такой тест, потому что делал подобное уже 100 раз. Гарантирую подлинное удовольствие каждому разработчику который будет это делать.
IonianWind
27.03.2018 18:12+3async function getFullPost(){ return await Promise.all([ fetch('/post'), fetch('/comments') ]); } const [post, comments] = getFullPost();
Так работать не будет
Нужно вызывать (внутри async-функции) так:
const [post, comments] = await getFullPost();
Или, на худой конец, так:
getFullPost() .then([post, comments] => { /*some code*/ }) .catch(error => { /*some code*/ });
Потому что функция getFullPost возвращает не массив, а Promise
wert_lex
27.03.2018 20:12Более того,
return await
считается антипаттерном, ибо не несёт никакого смысла.vitaliy2
27.03.2018 20:26-1В данном случае более чем несёт. При return await промис, созданный getFullPost, будет заполнен массивом с двумя значениями, а без return будет заполнен undefined.
В данном случае можно было бы вообще убрать async await, но это может:
- Нарушать стиль (раньше все async-функции использовали async и await, а теперь нет)
- Усложнять код (надо перечитывать код, чтобы убедиться, что мы возвращаем нужный промис, а при вызове функции используем await)
- Если потребуется добавить ещё await'ы в функцию, и тогда убранный async await придётся возвращать
mayorovp
27.03.2018 20:30Пункт 2 — мимо, потому что при вызове оператор await нужно ставить или не ставить независимо от того объявлена ли функция как async
vitaliy2
27.03.2018 20:39Имелось ввиду, что всем итак понятно, что async-функция возвращает промис, и если у нас 95% функций — это async, то когда попадётся асинхронная функция, не помеченная как async, ты сразу начнёшь думать, а нет ли такого, что я где-то в коде не заметил, что это на самом деле асинхронная функция, и с ней тоже надо было использовать await.
Т.?е. ты привык, что твои асинхронные функции выделены как async и уже на автомате используешь await с ними, а тут функция выбивается из стандартного шаблона — она не async, но всё-равно возвращает промис, и всё-равно надо использовать await.
Программисты любят, когда всё стандартизировано =)
Т.?е. получилось бы, что часть асинхронных функций написано так, а часть так, и вот это было бы странным. Проще везде поставить async и понадеяться, что оптимизатор вырежет лишний промис (вероятно, нет, т.?к. мы могли вернуть кастомный промис, а async-функция должна вернуть стандартный + могут не совпадать тайминги при двойной промисификации).
Ну и просто return await Promise может читаться лучше и быть меньше подвержен ошибкам. Поэтому может быть проще.
wert_lex
27.03.2018 20:48В данном случае более чем несёт. При return await промис, созданный getFullPost, будет заполнен массивом с двумя значениями, а без return будет заполнен undefined.
Нет, разницы никакой нет.
return await
"дождется" результатаPromise.all
и "завернёт" его в промис,return
ничего ждать не будет и сразу вернёт промис.
В кавычках, потому что это чуть иначе работает на самом деле, но смысл именно такой.vitaliy2
27.03.2018 20:54Я почему-то подумал про опускание return. Вообще да, если опустить await, результат будет тот же самый — он всё-равно дождётся этого промиса и только тогда заполнит автосозданный промис.
До появления официальной спецификации это, кстати, не поддерживалось, вроде, в Babel, и промис заполнялся промисом, а не его результатом.wert_lex
27.03.2018 21:02А тут чуть хитрее:
Promise<Promise<A>> === Promise<A>
vitaliy2
27.03.2018 21:09Ага, это встроено в сам промис, а не async: когда мы вызываем resolve(anotherPromise), он будет его ждать. Resolve в anotherPromise тоже при этом может ждать, и также и далее.
burdakovd
28.03.2018 13:40С точки зрения типизации выглядит адово и не нужно.
Почему язык за меня решает что я хотел вернуть
если может быть я действительно хотел вернутьPromise<A>
?Promise<Promise<A>>
Ну и
в общем случае не эквивалентенPromise<A>
, но если `A` в свою очередь является промисом, то срабатывает особая логика.A
Вам бы понравилось если бы скажем `push()` для массива определял случай когда аргумент является массивом, и делал `concat()` вместо `push()`?vitaliy2
29.03.2018 21:47Почему язык за меня решает что я хотел вернуть
Так сложилось исторически: когда async-await ещё не было, это было способом строить цепочки промисов, например:
promise.then(v => { return anotherPromise; }).then(v2 => { return someAnotherPromiseAgain; }).then(v3 => { … });
Если бы он не ждал anotherPromise, то в v2 мы бы получали не значение, а мгновенно этот промис, который в данном случае бесполезен.vitaliy2
29.03.2018 21:56Но даже тут в последнем примере мы блокируем функцию. Обычно это и требуется, но если вдруг не требуется, то чтобы получить итоговый промис, нам придётся либо выносить в отдельную функцию, либо использовать чейнинг. Можно и без чейнинга, но это было бы менее удобно.
В общем, логика есть. Предполагается, что мало кто захочет возвращать сам промис как значение, а вот чтобы функция его подождала — захотят действительно многие, потому что иногда это бывает очень полезно (особенно когда нет async-await — там без этого почти никак).
kirill89
28.03.2018 10:17Не совсем. Например при использовании try-catch делать return await очень даже оправдано:
async function t() { return Promise.reject(new Error('t')); } async function a() { try { return await t(); } catch(e) { console.log(`In a: ${e}`); } } async function b() { try { return t(); } catch(e) { console.log(`In b: ${e}`); } } a().catch(e => console.log(`Outside of a: ${e}`)); b().catch(e => console.log(`Outside of b: ${e}`)); // In a: Error: t // Outside of b: Error: t
Получается что наобороть return без await в async функциях — антипаттерн. Можно потерять catch. Я понимаю что это довольно редкий случай, но это делает его еще более опастным.wert_lex
29.03.2018 09:33Забавный нюанс, не знал и в дикой природе не встречал ни разу (к счастью?). Но если немного поразмыслить, то
a
иb
имеют немного разное поведение. Мне вот например не кажется очевидным, что во всех случаях надо ловить исключение, которое происходит в функции, промис из которой я возвращаю (function a
).
Но пример годный :)
abarkov
27.03.2018 18:28Я так понимаю что «Слияние Объектов» это то же самое что Object.assign?
Можно ли сделать вот так, чтоб не мутировать object1 и object2 или они и так не мутируют?
const object3 = { {}, ...object1, ...object2}
UPD: проверил, объекты не мутируют, крутая фича.wert_lex
27.03.2018 20:13Да, стандарт требует одинакового поведения для
{...a, ...b}
иObject.assign({}, a, b)
ReklatsMasters
27.03.2018 19:47В первом примере довольно не очевидно когда вызывается функция
required()
— в момент объявленияadd
или в момент вызова. Поэтому я предпочту не запутывать себя и буду контролировать аргументы внутри функции.vitaliy2
27.03.2018 20:45В момент вызова. Просто перечитайте доки.
pesh1983
28.03.2018 09:14Доки доками конечно, но когда одна и та же конструкция работает в разных контекстах по-разному — не очень хороший подход, как мне кажется. Просто потому, что это действительно запутывает. Ожидается одно поведение, а на практике оно другое, только из-за того, что контекст другой. По мне, лучше такие вещи не использовать, чтобы не запутывать ни себя, ни коллег, которые потом в таком коде будут разбираться.
vitaliy2
28.03.2018 14:42Не понял Вас. Оно всегда в момент вызова. Или Вы про другие языки?
pesh1983
29.03.2018 00:27Я про то, что поведение в данном контексте совсем неочевидно. В данном случае каждая функция, привязанная к аргументу как значение по умолчанию, выполняется каждый раз (или один раз?) при необходимости посчитать значение по умолчанию для аргумента, а не единожды в момент создания искомой функции с этими аргументами. И если в любом другом контексте выражение имя_функции() имеет однозначную трактовку, а именно исполнение кода этой функции сразу, как только интерпретатор добирается до этого места в коде, то в данном контексте оно так не работает.
Вдвойне запутывает ситуация, когда надо вычислить значения аргументов сразу при инициализации функции. Предположу, что это делается как-то так
const required = (p1, p2) => {...}; defaultA = required(v1, v2); defaultB = required(v3, v4); const add = (a = defaultA, b = defaultB) => a + b;
Но почему не тем синтаксисом, что описан в статье? Казалось бы, это же поведение конструкции по умолчанию.
По сути тут меняется поведение базовой конструкции в зависимости от контекста, что-то типа "оно работает так, но вот в этом конкретном случае — по-другому". Это ведёт к тому, что вам нужно ещё и контекст контролировать, что добавляет дополнительную бессмысленную сложность при чтении кода. И это при том, что во всех остальных языках, в которых такая конструкция именно вызывает функцию, это поведение соблюдается везде, независимо от контекста. И нет никакой двоякой трактовки, конструкция везде работает одинаково — выполняет функцию в том месте, где встречается это выражение.
Почему например, нельзя было придумать новый синтаксис для выполнения функции в контексте аргументов по умолчанию? Такое же уже делали (arrow functions, например). Тогда бы каждая конструкция однозначно трактовалась независимо от контекста. Как пример,
const required = () => {...}; const add = (a = @required, b = @required) => a + b;
И кстати, я не припомню больше таких конструкций в языке, которые так кардинально меняют свое поведение в зависимости от контекста, в котором выполняются.
vitaliy2
29.03.2018 00:35Я же говорю, в js оно вычисляется заново при каждом вызове функции. Вы что-то перепутали. Смотрите здесь раздел «Вычисляются во время вызова».
поведение меняется в зависимости от контекста
В том то и дело, что здесь поведение никогда не меняется: оно всегда вычисляется в момент вызова функции, и так в любом контексте. Если функция была вызвана 100 раз, то и будет вычислено 100 раз. Вы просто что-то перепутали.pesh1983
29.03.2018 09:42Да, вы правы. Все выражение вычисляется каждый раз по-новому, а не только при создании функции. Мне почему-то казалось, что это делается только для функций. Вот такая конструкция работает ожидаемо
let a = 1; let b = 2; let fn = (c = a + b) => c; fn() == 3 a = 2 fn() == 4
Но у такого подхода есть и обратная сторона. Переменные, которые использовались в качестве значений по умолчанию для аргументов, далее в коде менять нужно с большой осторожностью (если вообще стоит это делать), иначе при следующем вызове функции результат вычисления может быть совсем не тот, что ожидается. Другими словами,
let a = 1; let b = 2; let argA = a; // только так безопасно далее менять a let fn = (c = argA + b) => c; ... a = 2; // не скажется на результате вычисления функции
Выглядит, как костыль. Ну, а с вложенными функциями вообще беда получается
let fn = function (c = a + b) { let fn1 = (k = c + a) => k; ... // тут мы что-то могли сделать с 'c' return fn1(); } ... // ну и тут мы что-то могли сделать с 'a' и 'b' дополнительно
Что в итоге получится, никому не известно.
Как мне кажется, было бы удобнее и очевиднее, если бы значение вычислялось единожды при создании функции. А если нужна динамика, просто передаете в качестве аргумента функцию, указывая ее с новым синтаксисом, типа
let argFn = () => 1; let fn = (c = @argA) => c;
или разделить выполнение сразу и на каждый вызов, типа
let fn = (c = a + b) => c; // сразу let fn = (c = {a + b}) => c; // на каждый вызов
Это примеры, синтаксис можно придумать любой. Основная суть в том, чтобы добавить гибкости. Но все вышеперечисленное — это конечно, ИМХО.
vitaliy2
29.03.2018 11:32Потому что в качестве значения по умолчанию может передаваться массив или объект, и если он будет тем же самым, это совсем не то, что ожидает программист.
Ну а если нужны те же самые, никто не мешает сохранить во внешнюю переменную.
А те минусы, которые Вы привели — это просто от плохого знания замыканий. Для тех, кто давно пишет на js или других языках с замыканиями, такие вещи более чем очевидны. А вот кто недавно познакомился, пока ещё могут плохо понимать. Это не минус js. Сами по себе замыкания иногда очень полезны (без них не получится работать с функциями как с объектом), просто не нужно ожидать, что все языки одинаковые. Можете считать, что в js просто более развитое ООП (хотя приватные свойства, да, нельзя).
Ну и если Вы боитесь, что значение переменной может измениться, слово const вместо let тоже никто не отменял.
vitaliy2
27.03.2018 20:092.3. Использование reduce для анализа расстановки скобок
Конечно понятно, что приведённые пункты с reduce — это показание возможностей, а не советы, но раз уж на то пошло, возможно, вместо
return str.split('').reduce((counter, char) => {
стоит написать:
return [...str].reduce((counter, char) => {
Хотя результат не изменится, т.?к. при подсчёте скобок символы с кодом больше 65535 не повлияют (работать тоже, возможно, будет медленнее). Но зато наглядно показываются возможности ES6, а также делается намёк на существование суррогатных пар.
2.4. Подсчёт количества совпадающих значений массива
Здесь три обращения к хэшу (когда элемент дублируется):
obj[name] = obj[name] ? ++obj[name] : 1;
Поэтому правильнее написать так:
obj[name] = (obj[name] || 0) + 1;
Это два обращения к хэшу в любых случаях.
Devoter
28.03.2018 07:15Всё-таки, применение reduce во многих случаях спорно, особенно, в качестве замены map-filter. Если хотим производительности, то for уделяет их всех на две головы, если хотим наглядности — map-filter нагляднее.
LWXD
28.03.2018 10:10-1reduce работает быстрее чем for. Вот к примеру, результат времени работы reduce и for, в массиве из 10.000.000 где каждое значение умножается само на себя:
Время выполнения для reduce ~= 163.312ms
Время выполнения for ~= 282.706msevkochurov
28.03.2018 10:30На код можно взглянуть, с помощью которого сделан замер?
LWXD
28.03.2018 10:39Это конечно не самый идеальный вариант кода, но все же…
JSconst now = require('performance-now'); let array = []; for(let i = 0; i < 10000000; i++) { array.push(i); } let start = now(); var arr2 = array.reduce((num, item) => { return num * num; }, 1); let end = now(); console.log('Время выполнения = ' + (end - start).toFixed(3)); start = now(); let arr3 = []; for(let i = 0; i < 10000000; i++) { arr3.push(i * i); } end = now(); console.log('Время выполнения = ' + (end - start).toFixed(3));
justboris
28.03.2018 10:49У вас же две разных функциональности в коде
В reduce вы считаете факториал 10000000. Значение arr2 будет числом
var arr2 = array.reduce((num, item) => { return num * num; }, 1);
Во втором случае у вас будет массив с квадратами исходных чисел
let arr3 = []; for(let i = 0; i < 10000000; i++) { arr3.push(i * i); }
Это не разные операции, дают совсем разный результат, сравнивать их бессмысленно
LWXD
28.03.2018 10:56вот блин, точно. Не заметил сразу -_- надо же не num, а item умножать и + то что число в итоге выходит. Тогда да, for шустрее
LWXD
28.03.2018 13:00У меня с reduce тут совсем ужас получился
vitaliy2
28.03.2018 14:47push вообще медленная штука, т.?к. эквивалентен 5 операциям (по крайней мере в V8, который делает запас по длине массива 25%).
PS. Это во всех языках так, где есть динамический массив.
А в js ещё при записи в динамический массив всякие проверки добавляться могут (хотя бы на длину массива).
dom1n1k
28.03.2018 11:24Плюсую. За исключением самых простых случаев, reduce всегда очень неочевиден для понимания. Как возможность круто, но в реальном коде я бы его избегал.
AxisPod
28.03.2018 09:242.1 как-то совсем не по нраву, особенно учитывая, что выпиливается декларативность, код для восприятия становится только сложнее
Для reduce/reduceRight я бы привел в пример композицию методов для обратоки данных.
Keyten
28.03.2018 12:29Можно ещё и хитро маппить один объект в другой.
Например, хотим из
{ catName: 'Alex', fur: { color: 'black', length: 3 }, catAge: 8 }
Получить
{ type: 'cat', name: 'Alex', furColor: 'black', furLength: 3, age: 3 }
И при этом чтобы в случае отсутствия какого-то из полей в итоговом объекте не было полей, содержащих undefined.
Есть целых два способа записать это только деструктуризацией, без кучи многострочных ифов, в одну строку. Не скажу, что код получается сильно понятнее (хотя это больше дело привычки и знания такой конструкции), но сама возможность крутая.
function mapAnimal(source){ return { type: 'cat', ...(source.catName && { name: source.catName }), // и так далее }; }
Или даже как-то так:
function mapAnimal(source){ let {undefined, ...result} = { type: 'cat', [source.catName && 'name']: source.catName, [source.fur && source.fur.color && 'furColor']: source.fur.color }; return result; }
Можно ещё деструктурировать source прям в записи аргументов.
rockon404
28.03.2018 12:37Почему в примерах местами var, местами let, местами const?
var вообще в ES6 не надо использовать. let только если значение переменной переопределяется в коде. Для всех примеров следовало использовать const.Keyten
28.03.2018 19:54Чем вам var помешало?
rockon404
29.03.2018 16:18А скажите хоть один кейс когда лучше вместо let/const использовать var в ES6? Я ни одного не знаю и за все время работы с ES6 ни разу var не использовал. Наличие этого ключевого слова в спецификации обусловленно лишь обратной совместимостью с более ранними версиями. В современных гайдлайнах, вроде Airbnb, var не рекомендовано к использованию. Да и тема целесообразности его использования не раз поднималась во множестве статей и обсуждений, которые в большинстве своем сводятся к тому, что для var в ES6 нет места.
faiwer
29.03.2018 17:07+2Скорее всего вам ответят что-нибудь вроде:
try { // code var some = any(); // code } catch(err) { if(some) some.dispose(); }
Мотивируя это тем, что без
var
придётся выноситьlet some;
на уровень вышеtry-catch
и тем самым плодить "лишние строчки".
P.S.: По моему скромному мнению — строчки экономить не нужно. И предварительная декларация такого рода переменных лучше, чем по месту.
vlreshet
29.03.2018 17:41Кстати да, с самого начала интересно почему не предусмотрели этот момент. Можно же считать try-catch одним «блоком», с одной областью видимости. Не может же быть try без catch, или наоборот.
Keyten
30.03.2018 02:34Эмм, раз уж вы взялись предсказывать мой ответ, почему тогда try-catch, а не if-else?
faiwer
30.03.2018 07:16А зачем вам
var
вif-else
? В нём выполнится только 1 блок (либоif
, либоelse
), что вы там share-ить будете? :) И предсказывал я скорее не ваш ответ, а в целом ответ тех, кто используетvar
, к примеру vintage.vintage
30.03.2018 09:39-1var Foo = Foo || class Foo {}
try { var foo = bar() lol() } finally { return foo }
if( foo ) { var bar = lol() } else { var bar = zol() } console.log( bar )
for( var i = 0 ; i < list.length ; ++ i ) { if( foo( i ) ) break bar() } return list.slice( i )
faiwer
30.03.2018 09:50Да, точно, спасибо за примеры. О таком применении var в if-else я уже и забыл, слишком давно не использую.
Keyten
30.03.2018 02:34-1Вы зачем-то расширили моё утверждение. Я разве где-то утверждал, что он в чём-то лучше? Я скорее не понимаю, почему все стремятся использовать let вместо var везде, где нужно и где не нужно, если ошибки, связанные с var, попадаются очень редко, очень легко ловятся, и вообще достигаются, в основном, новичками в языке, не потрудившимися прочитать, как оно тут работает.
Это примерно как возмутиться, что у массивов могут быть повторяющиеся значения, и поэтому рекомендовать всегда вместо него использовать Set.
Моё мнение: они вполне равноправны. Да, где-то let бывает писать удобнее. Но ведь где-то и с var такая же ситуация! (а вот теперь утверждаю, да: всплытие на уровне функции действительно бывает удобным)faiwer
30.03.2018 07:20+1всплытие на уровне функции действительно бывает удобным
Эта тема немного холиварная. Большинство из тех, кто не использует
var
, считают, что "удобство" в ущерб очевидности, однозначности, понятности кода — скорее зло. В случаеvar
едва ли оно обоснованное. До того какlet
иconst
в языке появились, это была частая причина поливать JS грязью.
Кстати, в PHP вообще даже
var
писать не нужно. Просто пишешь$mySuperVariable =
и радуешься жизни. "Удобно" :)
k12th
29.03.2018 17:14Тем, что это конструкция с неочевидным поведением, которая породила множество идиотских задачек в тестах и собеседованиях типа "так никто никогда не пишет, но мы хотим чтоб вы разобрались в этом говнокоде", а так же одно из самых идиотских в истории линтеров правил в jslint.
vintage
30.03.2018 09:56Вот вам задачка для собеседования на одних let-ах:
let x = 1 switch( x ) { case 1 : let x = 2 console.info( x ) break case 2 : let x = 3 console.debug( x ) break default : console.log( x ) }
Opty
28.03.2018 13:14П.3.1 — несет кучу мусора, изначально непонятно зачем присвоение в переменные, которые потом не используются. А вот 2.3 порадовало, спасибо)
gmixo
28.03.2018 14:203.2. Деструктурирование вложенных объектов в параметрах функции — небезопасно когда более одной вложенности
car={'model': 'повозка'} modelAndVIN = ({model, engine: {vin}}) => { console.log(`model: ${model} vin: ${vin}`); } modelAndVIN(car) Uncaught TypeError: Cannot destructure property `vin` of 'undefined' or 'null'.
k12th
Строго говоря,
async
/await
и...
для объектов в ES6 (то есть ES2015) не входят. Первое попало только в ES2017, второе будет в ES2018.А reduce крутая вещь, да.
staticlab
Но reduce был ещё в ES5.
k12th
Действительно. В общем, не очень точное название у статьи:)