Pull to refresh

Comments 87

Строго говоря, async/await и ... для объектов в ES6 (то есть ES2015) не входят. Первое попало только в ES2017, второе будет в ES2018.


А reduce крутая вещь, да.

UFO just landed and posted this here

Действительно. В общем, не очень точное название у статьи:)

UFO just landed and posted this here
UFO just landed and posted this here
Вообще, проверка как в статье бесполезна, т. к. чаще всего, если делается проверка аргумента, то будет сделана и проверка типа, и даже, возможно, диапазона значений. Здесь же только проверка, что не undefined, да и со стандартной ошибкой, что как-то скупо…

Но если бы можно было обозначать примерно в таком стиле, то мб и пошло:
function(arg!, otherArg!) {}

Тоже только на undefined, но короче и с нормальной ошибкой.

Почему бы и да?


const required = varName => throw new Error(`Variable ${varName} shouldnt be undefined`);

function myFunc(a = required('a')){...}
Два раза повторять название параметра, не очень удобно.
var filtered = [...mySet].filter((x) => x > 3) // [4, 5]

напишите тестик на производительность этого на сете хотяб на пару тысяч элементов, а лучше больше, по сравнению с foreach по этому сету где сложат всё нужное в отдельный заранее созданный массив. И прогоните на современных браузерах включая мобилки. Я прям вот специально сейчас не пойду и не буду писать такой тест, потому что делал подобное уже 100 раз. Гарантирую подлинное удовольствие каждому разработчику который будет это делать.
async 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

Я конечно не спец по джаваскрипту. Но вроде getFullPost возвращает уже выполненный (не уверен как правильно назвать) промис?
Там стоит return await Promise...

А перед этой функцией стоит async. Такая функция всегда возвращает промис.

Более того, return await считается антипаттерном, ибо не несёт никакого смысла.

В данном случае более чем несёт. При return await промис, созданный getFullPost, будет заполнен массивом с двумя значениями, а без return будет заполнен undefined.

В данном случае можно было бы вообще убрать async await, но это может:

  1. Нарушать стиль (раньше все async-функции использовали async и await, а теперь нет)
  2. Усложнять код (надо перечитывать код, чтобы убедиться, что мы возвращаем нужный промис, а при вызове функции используем await)
  3. Если потребуется добавить ещё await'ы в функцию, и тогда убранный async await придётся возвращать

Пункт 2 — мимо, потому что при вызове оператор await нужно ставить или не ставить независимо от того объявлена ли функция как async
Имелось ввиду, что всем итак понятно, что async-функция возвращает промис, и если у нас 95% функций — это async, то когда попадётся асинхронная функция, не помеченная как async, ты сразу начнёшь думать, а нет ли такого, что я где-то в коде не заметил, что это на самом деле асинхронная функция, и с ней тоже надо было использовать await.

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

Программисты любят, когда всё стандартизировано =)
Т. е. получилось бы, что часть асинхронных функций написано так, а часть так, и вот это было бы странным. Проще везде поставить async и понадеяться, что оптимизатор вырежет лишний промис (вероятно, нет, т. к. мы могли вернуть кастомный промис, а async-функция должна вернуть стандартный + могут не совпадать тайминги при двойной промисификации).

Ну и просто return await Promise может читаться лучше и быть меньше подвержен ошибкам. Поэтому может быть проще.
В данном случае более чем несёт. При return await промис, созданный getFullPost, будет заполнен массивом с двумя значениями, а без return будет заполнен undefined.

Нет, разницы никакой нет.
return await "дождется" результата Promise.all и "завернёт" его в промис, return ничего ждать не будет и сразу вернёт промис.
В кавычках, потому что это чуть иначе работает на самом деле, но смысл именно такой.

Я почему-то подумал про опускание return. Вообще да, если опустить await, результат будет тот же самый — он всё-равно дождётся этого промиса и только тогда заполнит автосозданный промис.

До появления официальной спецификации это, кстати, не поддерживалось, вроде, в Babel, и промис заполнялся промисом, а не его результатом.

А тут чуть хитрее: Promise<Promise<A>> === Promise<A>

Ага, это встроено в сам промис, а не async: когда мы вызываем resolve(anotherPromise), он будет его ждать. Resolve в anotherPromise тоже при этом может ждать, и также и далее.
С точки зрения типизации выглядит адово и не нужно.

Почему язык за меня решает что я хотел вернуть
Promise<A>
если может быть я действительно хотел вернуть
Promise<Promise<A>>
?

Ну и
Promise<A>
в общем случае не эквивалентен
A
, но если `A` в свою очередь является промисом, то срабатывает особая логика.

Вам бы понравилось если бы скажем `push()` для массива определял случай когда аргумент является массивом, и делал `concat()` вместо `push()`?
Зато concat умеет делать push если аргумент не является массивом…
Почему язык за меня решает что я хотел вернуть
Так сложилось исторически: когда async-await ещё не было, это было способом строить цепочки промисов, например:
promise.then(v => {
	return anotherPromise;
}).then(v2 => {
	return someAnotherPromiseAgain;
}).then(v3 => {
	…
});

Если бы он не ждал anotherPromise, то в v2 мы бы получали не значение, а мгновенно этот промис, который в данном случае бесполезен.

Или что-то типа такого:
fetch(…).then(res => res.json()).then(json => { … });

res.json() возвращает промис, потому что оно не может мгновенно вернуть json, потому что данные ещё не прочитаны из сети или читаются как поток. А мы хотим этот промис подождать.

С async-await это можно сделать так:
await (await fetch(…)).json();
Но даже тут в последнем примере мы блокируем функцию. Обычно это и требуется, но если вдруг не требуется, то чтобы получить итоговый промис, нам придётся либо выносить в отдельную функцию, либо использовать чейнинг. Можно и без чейнинга, но это было бы менее удобно.

В общем, логика есть. Предполагается, что мало кто захочет возвращать сам промис как значение, а вот чтобы функция его подождала — захотят действительно многие, потому что иногда это бывает очень полезно (особенно когда нет async-await — там без этого почти никак).
Не совсем. Например при использовании 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. Я понимаю что это довольно редкий случай, но это делает его еще более опастным.

Забавный нюанс, не знал и в дикой природе не встречал ни разу (к счастью?). Но если немного поразмыслить, то a и b имеют немного разное поведение. Мне вот например не кажется очевидным, что во всех случаях надо ловить исключение, которое происходит в функции, промис из которой я возвращаю (function a).


Но пример годный :)

ru_vds Надо исправить, люди же читают и запоминают неправильно!
Отличная, статья. Без воды, как в принципе и все остальные Ваши статьи.
Я так понимаю что «Слияние Объектов» это то же самое что Object.assign?
Можно ли сделать вот так, чтоб не мутировать object1 и object2 или они и так не мутируют?

const object3 = { {}, ...object1, ...object2}


UPD: проверил, объекты не мутируют, крутая фича.

Первый пустой объект вам не нужен, он и так уже как бы объявлен окружающими скобками


const object3 = { ...object1, ...object2 }

Babel как раз это разложит в Object.assign({}, object1, object2). Пример

Да, стандарт требует одинакового поведения для {...a, ...b} и Object.assign({}, a, b)

UFO just landed and posted this here
В момент вызова. Просто перечитайте доки.
Доки доками конечно, но когда одна и та же конструкция работает в разных контекстах по-разному — не очень хороший подход, как мне кажется. Просто потому, что это действительно запутывает. Ожидается одно поведение, а на практике оно другое, только из-за того, что контекст другой. По мне, лучше такие вещи не использовать, чтобы не запутывать ни себя, ни коллег, которые потом в таком коде будут разбираться.
Не понял Вас. Оно всегда в момент вызова. Или Вы про другие языки?

Я про то, что поведение в данном контексте совсем неочевидно. В данном случае каждая функция, привязанная к аргументу как значение по умолчанию, выполняется каждый раз (или один раз?) при необходимости посчитать значение по умолчанию для аргумента, а не единожды в момент создания искомой функции с этими аргументами. И если в любом другом контексте выражение имя_функции() имеет однозначную трактовку, а именно исполнение кода этой функции сразу, как только интерпретатор добирается до этого места в коде, то в данном контексте оно так не работает.
Вдвойне запутывает ситуация, когда надо вычислить значения аргументов сразу при инициализации функции. Предположу, что это делается как-то так


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;

И кстати, я не припомню больше таких конструкций в языке, которые так кардинально меняют свое поведение в зависимости от контекста, в котором выполняются.

Я же говорю, в js оно вычисляется заново при каждом вызове функции. Вы что-то перепутали. Смотрите здесь раздел «Вычисляются во время вызова».

поведение меняется в зависимости от контекста
В том то и дело, что здесь поведение никогда не меняется: оно всегда вычисляется в момент вызова функции, и так в любом контексте. Если функция была вызвана 100 раз, то и будет вычислено 100 раз. Вы просто что-то перепутали.

Да, вы правы. Все выражение вычисляется каждый раз по-новому, а не только при создании функции. Мне почему-то казалось, что это делается только для функций. Вот такая конструкция работает ожидаемо


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; // на каждый вызов

Это примеры, синтаксис можно придумать любой. Основная суть в том, чтобы добавить гибкости. Но все вышеперечисленное — это конечно, ИМХО.

Потому что в качестве значения по умолчанию может передаваться массив или объект, и если он будет тем же самым, это совсем не то, что ожидает программист.

Ну а если нужны те же самые, никто не мешает сохранить во внешнюю переменную.

А те минусы, которые Вы привели — это просто от плохого знания замыканий. Для тех, кто давно пишет на js или других языках с замыканиями, такие вещи более чем очевидны. А вот кто недавно познакомился, пока ещё могут плохо понимать. Это не минус js. Сами по себе замыкания иногда очень полезны (без них не получится работать с функциями как с объектом), просто не нужно ожидать, что все языки одинаковые. Можете считать, что в js просто более развитое ООП (хотя приватные свойства, да, нельзя).

Ну и если Вы боитесь, что значение переменной может измениться, слово const вместо let тоже никто не отменял.
Потому что в качестве значения по умолчанию может передаваться массив или объект, и если он будет тем же самым, это совсем не то, что ожидает программист.

Вот тут не совсем я вас понял. Разве в JS все сложные объекты передаются не по ссылке? То есть при указании объекта в качестве параметра по умолчанию это будет один и тот же объект в любом случае. Ну просто потому, что сложные объекты иначе не передаются, только по ссылке. А теперь это поведение добавлено еще и для простых типов в качестве значений по умолчанию (через замыкание, как вы и написали ниже).


Ну а если нужны те же самые, никто не мешает сохранить во внешнюю переменную.

Можете привести пример кода. Не совсем понятно, кусочек кода многое бы прояснил) Спасибо


А те минусы, которые Вы привели — это просто от плохого знания замыканий.

Я представляю, как работают замыкания) Но есть подход, когда замыкание используется явно, то есть


let a = 1;
let fn = function () {
    let c = a + 1;
}

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


let a = 1;
let independentA = a;
let fn = function (k = independentA) {
    ...
}

А взять именно значение из 'a', не привязываясь к 'a' по ссылке никак по-другому нельзя без копирования значения в отдельную переменную и уже использование ее в качестве значения по умолчанию.
Если вы напишете код, которые берет именно значение из 'а', при этом не привязываясь к самой ссылке 'a', я буду вам благодарен.


Ну и если Вы боитесь, что значение переменной может измениться, слово const вместо let тоже никто не отменял.

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


const argA = a;
fn = function (b = argA) {
    ...
}

В Python это без всяких костылей делается


a = 1
def fn(b = a):
    return b

fn() == 1
a = 10
fn() == 1

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


a = 1
def fn(b = None):
    if b is None:
        b = a
    return b

fn() == 1
a = 10
fn() == 10
Вот тут не совсем я вас понял. Разве в JS все сложные объекты передаются не по ссылке? То есть при указании объекта в качестве параметра по умолчанию это будет один и тот же объект в любом случае. Ну просто потому, что сложные объекты иначе не передаются, только по ссылке. А теперь это поведение добавлено еще и для простых типов в качестве значений по умолчанию (через замыкание, как вы и написали ниже).
В том то и дело, что в параметрах по умолчанию объект создаётся каждый раз заново.

function test(a = []) {
	a.push(6, 7);
	console.log(a); //Всегда будет выводить [6, 7], а не [6, 7, 6, 7, 6, 7…]
}

Если бы значение вычислялось один раз, то выводилось бы [6, 7, 6, 7, 6, 7…], а так каждый раз выводится [6, 7].

Все сложные объекты передаются по ссылке
В js все объекты передаются по ссылке вне зависимости от сложности.

Если ему хочется передать именно значение из переменной как значение аргумента по умолчанию, а не как ссылку на объект
Да, с помощью переменной в замыкании:
const cachedA = calc(1,2,3,4,5);

function test(a = cachedA) {
	//…
}

Конечно же, если test обёрнут в другую функцию, и эта функция вызывается много раз, то cachedA может быть разным. Но в одном конкретно взятом контексте оно никогда не меняется и не вычисляется заново.

Но есть подход, когда замыкание используется явно. А в случае аргументов по умолчанию у программиста нет выбора.
Эм, вообще-то аргументы по умолчанию — это просто синтаксический сахар. Например, такая функция
function test(a = [], b = 5) {
	//…
}

полностью эквивалентна такой:
function test(a, b) {
	if (a === undefined) a = [];
	if (b === undefined) b = 5;
	
	//…
}

Если вы напишете код, которые берет именно значение из 'а', при этом не привязываясь к самой ссылке 'a', я буду вам благодарен.
Что? Как можно взять значение из a, не обратившись к нему?

В Python это без всяких костылей делается
Без костылей? Да. А как тогда без костылей сделать в питоне так, чтобы поведение было обратным? КАК? Вы ниже написали как, но Вам пришлось нагородить такие костыли, что аж страшно…) С js'ом это явно не сравнится =)
В js все объекты передаются по ссылке вне зависимости от сложности.

Если бы было так, как вы написали, то было бы так


let a = 1;
let b = a;
a = 2;
b == 2;  // true

однако, оно не так) Потому что это присвоение по значению и работает оно только для объектов простых типов. Для сложных типа array, object передается по ссылке. Хотя, мне кажется, это вам итак известно, и почему вы такую чушь написали, для меня загадка.


Что? Как можно взять значение из a, не обратившись к нему?

Я разве вам предлагал не обращаться к нему? Если я такое написал, позор мне, ткните меня пальцем в мой же текст)


Эм, вообще-то аргументы по умолчанию — это просто синтаксический сахар.

Ааа, ну вот сразу бы так и написали, что это обычный синтаксический сахар для костылей, а не нормальное определение значений по умолчанию, как в других языках. Тогда вопрос закрыт)

Если бы было так, как вы написали, то было бы так
Вы абсолютно неправы. Смотрите, в первой строке Вы присваиваете a ссылку на 1, во второй тоже ссылку на один.

В данный момент a хранит ссылку на 1 и b хранит ссылку на 1. Дальше Вы в a присваиваете ссылку на 2. Но в b так и осталась ссылка на 1. Далее, когда Вы сравниваете a == b, Вы сравниваете ссылку на 1 и ссылку на 2 — очевидно, что ответ будет false.

Ну а вообще объект — это всё, кроме числа. Числа в js тоже можно рассматривать как объекты, но которые не могут иметь несколько одинаковых дубликатов. Аналогично и со строками (внутри движка они на самом деле иногда могут, но при сравнении проверяется, что они точно не дубликаты).

Ааа, ну вот сразу бы так и написали, что это обычный синтаксический сахар для костылей
Поправочка: сахар для костылей, который делает их не костылями.
В данный момент a хранит ссылку на 1 и b хранит ссылку на 1. Дальше Вы в a присваиваете ссылку на 2. Но в b так и осталась ссылка на 1.

Ваша правда. Напутал все на свете) Мутабельность объекта перепутал с иммутабельностью строк и чисел) Ну вроде того


let a = {"k": 1};
let b = a;
a["k"] = 2;
b["k"] == 2;  // true

Вечер пятницы) Голова уж плохо соображает, прошу прощения)
Как вы и указали, это просто синтаксический сахар, поэтому ожидать от него поведения, отличающегося от оригинала "без сахара" глупо. Это все объясняет. Мне почему-то сначала показалось, что новую возможность завезли.

Как мне кажется, было бы удобнее и очевиднее, если бы значение вычислялось единожды при создании функции.

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

То есть при указании объекта в качестве параметра по умолчанию это будет один и тот же объект в любом случае.

При указании ССЫЛКИ на объект в качестве значения по умолчанию — значением будет эта же самая ссылка, что естественно.
А вот если я введу литерал объекта — объект будет создаваться каждый раз новый. Что, опять-таки, естественно.

let fn = (c = {})=>(c);
let a = fn(), b = fn();

a==b// false


А взять именно значение из 'a', не привязываясь к 'a' по ссылке никак по-другому нельзя без копирования значения в отдельную переменную и уже использование ее в качестве значения по умолчанию.


Простите, Вы давно на JS пишете? А то прямо как будто про задачку с собеседования говорите.
«Напишите функцию, которая создаст массив из N функций, каждая из которых возвращает всегда одно и то же число, равное её номеру в упомянутом массиве».

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

Задача сделать чистую функцию fn, чтобы она взяла значение из 'a' и использовала его всегда в дальнейшем, независимо от дальнейшего изменения 'а', то есть не зависела от внешнего контекста. Внимание, нужно именно первоначальное значение из 'а', потому что функция должна быть чистая, то есть независимо от внешнего контекста всегда возвращать один и то же результат при одинаковых входных параметрах. Иначе функция подвержена сайд эффекту из контекста выше, а нам это не нужно по задаче.


let a = 1;
let fn = function (c = a) {
  return c + 1;
}
fn() == 1;
a = 10;
fn() == 11;

Представьте, что значение в 'а' нам не известно на этапе написания кода, что вполне логично, это ж переменная.

Может я чего-то не догоняю, но это мной воспринимается как какой-то треш.
Поделитесь каким-нибудь менее абстрактным примером.
В каком случае имеет смысл написать
let a = 1;
let fn = function (c = a) {
  return c + 1;
}

Но нельзя написать
let a = 1;
let fn = function (c = 1) {
  return c + 1;
}

или
let a = ()=>(1);
let fn = function (c = a()) {
  return c + 1;
}

?

Ну хорошо, перепишу пример, чтобы было понятно, что это именно ПЕРЕМЕННАЯ, а не значение именно 1, которые вы пишете) Вот так понятнее?


let a = $input.val();
let fn = function (c = a) {
  return c + 1;
}

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

И вы кстати так и решили поставленную задачу)

И не намерен, потому что саму постановку такой задачи считаю безумием.
Если мне нужна переменная, чтобы хранить некоторую настройку — например, значение по умолчанию для группы функций, то эта переменная должна использоваться ТОЛЬКО для хранения этой настройки.
Если же хранить эту настройку на протяжение всего времени работы программы не нужно — то и переменная не нужна.
2.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;

Это два обращения к хэшу в любых случаях.

Всё-таки, применение reduce во многих случаях спорно, особенно, в качестве замены map-filter. Если хотим производительности, то for уделяет их всех на две головы, если хотим наглядности — map-filter нагляднее.

reduce работает быстрее чем for. Вот к примеру, результат времени работы reduce и for, в массиве из 10.000.000 где каждое значение умножается само на себя:
Время выполнения для reduce ~= 163.312ms
Время выполнения for ~= 282.706ms
На код можно взглянуть, с помощью которого сделан замер?
Это конечно не самый идеальный вариант кода, но все же…
JS
const 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));

У вас же две разных функциональности в коде


В 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);
}

Это не разные операции, дают совсем разный результат, сравнивать их бессмысленно

вот блин, точно. Не заметил сразу -_- надо же не num, а item умножать и + то что число в итоге выходит. Тогда да, for шустрее

У меня с reduce тут совсем ужас получился

push вообще медленная штука, т. к. эквивалентен 5 операциям (по крайней мере в V8, который делает запас по длине массива 25%).

PS. Это во всех языках так, где есть динамический массив.

А в js ещё при записи в динамический массив всякие проверки добавляться могут (хотя бы на длину массива).
Плюсую. За исключением самых простых случаев, reduce всегда очень неочевиден для понимания. Как возможность круто, но в реальном коде я бы его избегал.
2.1 как-то совсем не по нраву, особенно учитывая, что выпиливается декларативность, код для восприятия становится только сложнее

Для reduce/reduceRight я бы привел в пример композицию методов для обратоки данных.

Можно ещё и хитро маппить один объект в другой.
Например, хотим из


{ 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 прям в записи аргументов.

Почему в примерах местами var, местами let, местами const?
var вообще в ES6 не надо использовать. let только если значение переменной переопределяется в коде. Для всех примеров следовало использовать const.
А скажите хоть один кейс когда лучше вместо let/const использовать var в ES6? Я ни одного не знаю и за все время работы с ES6 ни разу var не использовал. Наличие этого ключевого слова в спецификации обусловленно лишь обратной совместимостью с более ранними версиями. В современных гайдлайнах, вроде Airbnb, var не рекомендовано к использованию. Да и тема целесообразности его использования не раз поднималась во множестве статей и обсуждений, которые в большинстве своем сводятся к тому, что для var в ES6 нет места.

Скорее всего вам ответят что-нибудь вроде:


try
{
  // code
  var some = any();
  // code
}
catch(err)
{
  if(some)
    some.dispose();
}

Мотивируя это тем, что без var придётся выносить let some; на уровень выше try-catch и тем самым плодить "лишние строчки".


P.S.: По моему скромному мнению — строчки экономить не нужно. И предварительная декларация такого рода переменных лучше, чем по месту.

UFO just landed and posted this here
Эмм, раз уж вы взялись предсказывать мой ответ, почему тогда try-catch, а не if-else?

А зачем вам var в if-else? В нём выполнится только 1 блок (либо if, либо else), что вы там share-ить будете? :) И предсказывал я скорее не ваш ответ, а в целом ответ тех, кто использует var, к примеру vintage.

var 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 )

Да, точно, спасибо за примеры. О таком применении var в if-else я уже и забыл, слишком давно не использую.

Только в таком применении по сравнению с этим:
let bar;
if(foo) {
  bar = lol();
} else {
  bar = zol();
}
cosole.log(bar);

кода меньше ровно на один символ переноса строки. А гораздо читаемей, по моему скромному мнению, будет так:
const bar = foo ? lol() : zol();

console.log(bar);

Так товарищи выше не символы экономят. Не-не. Они строчки экономят. Дескать чем меньше строк, тем больше на экран влезет. Ну и тем меньше глазами бегать искать чего-то. На мой взгляд вынесенная отдельно let bar даёт чётко понять, что она там не спроста, и её судьба будет определена вариативно чуть ниже. Я за let :)


А гораздо читаемей, по моему скромному мнению, будет так

Ну тут вы уже нечестно играете. В реальном коде там будут не только bar = lol(); и bar = zol();. Там будет много всего, и тернарника вам не хватит :) Тернарный оператор вообще очень быстро становится не читаемой грудой мусора, с вовлечением чего-нибудь не тривиального. Всякие там аргументы, выражения и пр… Я вот жду do-нотации. Она выглядит вкусно.

Так товарищи выше не символы экономят. Не-не. Они строчки экономят. Дескать чем меньше строк, тем больше на экран влезет. Ну и тем меньше глазами бегать искать чего-то.

Зачем вы продолжаете пытаться читать чужие мысли, если вам уже сказали, что у вас не получается?
Вы зачем-то расширили моё утверждение. Я разве где-то утверждал, что он в чём-то лучше? Я скорее не понимаю, почему все стремятся использовать let вместо var везде, где нужно и где не нужно, если ошибки, связанные с var, попадаются очень редко, очень легко ловятся, и вообще достигаются, в основном, новичками в языке, не потрудившимися прочитать, как оно тут работает.
Это примерно как возмутиться, что у массивов могут быть повторяющиеся значения, и поэтому рекомендовать всегда вместо него использовать Set.

Моё мнение: они вполне равноправны. Да, где-то let бывает писать удобнее. Но ведь где-то и с var такая же ситуация! (а вот теперь утверждаю, да: всплытие на уровне функции действительно бывает удобным)
всплытие на уровне функции действительно бывает удобным

Эта тема немного холиварная. Большинство из тех, кто не использует var, считают, что "удобство" в ущерб очевидности, однозначности, понятности кода — скорее зло. В случае var едва ли оно обоснованное. До того как let и const в языке появились, это была частая причина поливать JS грязью.


Кстати, в PHP вообще даже var писать не нужно. Просто пишешь $mySuperVariable = и радуешься жизни. "Удобно" :)

Хорошо. Объясните где, по-вашему, в ES6 нужно использовать var, где не нужно использовать const/let?
Любое подобное утверждение (в том числе и «всегда используйте let») — это в немалой степени вкусовщина. Ну потому что по сути ничего не меняется кроме «мне кажется, что так красивее».

Моё имхо насчёт использования следующее:

1. const для любых констант, которые вряд ли может понадобиться менять. Например, число пи. Не могу представить адекватной ситуации, когда бы мне захотелось изменить значение этой константы.
Но при этом не использовать для всех-всех значений, которые не меняются конкретно в этом коде (мы можем что-нибудь захотеть изменить в коде, и нам придётся менять определение переменной).
2. let — когда видимость внутри блока существенно важна. Например, в цикле for, если внутри setTimeout.
3. var — во всех остальных случаях.
UFO just landed and posted this here
Если это не говнокод на кучке глобальных переменных — то зачем ему может понадобится var?

Для красоты. Чтобы явно обозначить переменную, объявленную на уровне процедуры. Хотя, в принципе, в этом случае использования, никакой разницы с let нет.
Опыт показывает что часто при дебаге чужого кода приходится перелистывать весь код, чтобы понять не изменили ли где-то по пути такую-то переменную. А если начать использовать const — то вы удивитесь, как редко, на самом деле, вам нужны изменяемые переменные кроме как в циклах да в аргументах функций.

Вы удивитесь, но у меня ровно противоположный опыт. Смотреть, не изменилась ли переменная, приходится примерно никогда (да и зачем, если можно исполнить код и вывести её в консоль?), зато почти каждый день приходится лезть выше по коду и исправлять const на let, потому что модифицирую код, и внезапно теперь какую-то переменную нужно менять.
А уж в консоли вообще весело. Объявишь какую-нибудь переменную по привычке через const, а потом чертыхаешься, потому что её понадобилось поменять, а уже никак.

Если это не говнокод на кучке глобальных переменных — то зачем ему может понадобится var?

ться.
Для того, чтобы объявлять переменные. Ещё раз: от замены let на var в коде почти всегда не меняется примерно ничего. Хотя можете привести какой-нибудь пример, когда у вас реальный настоящий код, скажем, от замены var на let, стал в 2 раза чище и короче. Есть такие примеры?
Никаких багов не будет, если вы знаете, как работает var.

Тем, что это конструкция с неочевидным поведением, которая породила множество идиотских задачек в тестах и собеседованиях типа "так никто никогда не пишет, но мы хотим чтоб вы разобрались в этом говнокоде", а так же одно из самых идиотских в истории линтеров правил в jslint.

Вот вам задачка для собеседования на одних 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 )
}
П.3.1 — несет кучу мусора, изначально непонятно зачем присвоение в переменные, которые потом не используются. А вот 2.3 порадовало, спасибо)
3.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'.
Да. Поэтому, если и делать так, то для всех верхних узлов нужно указывать значение по-умолчанию:
car={'model': 'повозка'}

modelAndVIN = ({model, engine: {vin} = {}}) => {
  console.log(`model: ${model} vin: ${vin}`);
}

modelAndVIN(car)
Only those users with full accounts are able to leave comments. Log in, please.