Comments 87
Строго говоря, async
/await
и ...
для объектов в ES6 (то есть ES2015) не входят. Первое попало только в ES2017, второе будет в ES2018.
А reduce крутая вещь, да.
Но если бы можно было обозначать примерно в таком стиле, то мб и пошло:
function(arg!, otherArg!) {}
Тоже только на undefined, но короче и с нормальной ошибкой.
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...
Более того, return await
считается антипаттерном, ибо не несёт никакого смысла.
В данном случае можно было бы вообще убрать async await, но это может:
- Нарушать стиль (раньше все async-функции использовали async и await, а теперь нет)
- Усложнять код (надо перечитывать код, чтобы убедиться, что мы возвращаем нужный промис, а при вызове функции используем await)
- Если потребуется добавить ещё await'ы в функцию, и тогда убранный async await придётся возвращать
Т. е. ты привык, что твои асинхронные функции выделены как async и уже на автомате используешь await с ними, а тут функция выбивается из стандартного шаблона — она не async, но всё-равно возвращает промис, и всё-равно надо использовать await.
Программисты любят, когда всё стандартизировано =)
Т. е. получилось бы, что часть асинхронных функций написано так, а часть так, и вот это было бы странным. Проще везде поставить async и понадеяться, что оптимизатор вырежет лишний промис (вероятно, нет, т. к. мы могли вернуть кастомный промис, а async-функция должна вернуть стандартный + могут не совпадать тайминги при двойной промисификации).
Ну и просто return await Promise может читаться лучше и быть меньше подвержен ошибкам. Поэтому может быть проще.
В данном случае более чем несёт. При return await промис, созданный getFullPost, будет заполнен массивом с двумя значениями, а без return будет заполнен undefined.
Нет, разницы никакой нет.
return await
"дождется" результата Promise.all
и "завернёт" его в промис, return
ничего ждать не будет и сразу вернёт промис.
В кавычках, потому что это чуть иначе работает на самом деле, но смысл именно такой.
До появления официальной спецификации это, кстати, не поддерживалось, вроде, в Babel, и промис заполнялся промисом, а не его результатом.
А тут чуть хитрее: Promise<Promise<A>> === Promise<A>
Почему язык за меня решает что я хотел вернуть
Promise<A>
если может быть я действительно хотел вернуть Promise<Promise<A>>
?Ну и
Promise<A>
в общем случае не эквивалентен A
, но если `A` в свою очередь является промисом, то срабатывает особая логика.Вам бы понравилось если бы скажем `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 — там без этого почти никак).
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
).
Но пример годный :)
Можно ли сделать вот так, чтоб не мутировать object1 и object2 или они и так не мутируют?
const object3 = { {}, ...object1, ...object2}
UPD: проверил, объекты не мутируют, крутая фича.
Я про то, что поведение в данном контексте совсем неочевидно. В данном случае каждая функция, привязанная к аргументу как значение по умолчанию, выполняется каждый раз (или один раз?) при необходимости посчитать значение по умолчанию для аргумента, а не единожды в момент создания искомой функции с этими аргументами. И если в любом другом контексте выражение имя_функции() имеет однозначную трактовку, а именно исполнение кода этой функции сразу, как только интерпретатор добирается до этого места в коде, то в данном контексте оно так не работает.
Вдвойне запутывает ситуация, когда надо вычислить значения аргументов сразу при инициализации функции. Предположу, что это делается как-то так
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;
И кстати, я не припомню больше таких конструкций в языке, которые так кардинально меняют свое поведение в зависимости от контекста, в котором выполняются.
поведение меняется в зависимости от контекстаВ том то и дело, что здесь поведение никогда не меняется: оно всегда вычисляется в момент вызова функции, и так в любом контексте. Если функция была вызвана 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. Если бы присвоение аргументу значения по умолчанию выполнялось единожды при создании функции, то этой проблемы бы не было) Но оно через замыкание выполняется каждый раз при вызове фукнции. Впрочем, выше уже написали, что это синтаксический сахар, а не новая конструкция языка. А я уж думал...
И вы кстати так и решили поставленную задачу)
И не намерен, потому что саму постановку такой задачи считаю безумием.
Если мне нужна переменная, чтобы хранить некоторую настройку — например, значение по умолчанию для группы функций, то эта переменная должна использоваться ТОЛЬКО для хранения этой настройки.
Если же хранить эту настройку на протяжение всего времени работы программы не нужно — то и переменная не нужна.
Конечно понятно, что приведённые пункты с 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 ~= 163.312ms
Время выполнения for ~= 282.706ms
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);
}
Это не разные операции, дают совсем разный результат, сравнивать их бессмысленно
У меня с reduce тут совсем ужас получился
Для 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 вообще в ES6 не надо использовать. let только если значение переменной переопределяется в коде. Для всех примеров следовало использовать const.
Чем вам var помешало?
Скорее всего вам ответят что-нибудь вроде:
try
{
// code
var some = any();
// code
}
catch(err)
{
if(some)
some.dispose();
}
Мотивируя это тем, что без var
придётся выносить let some;
на уровень выше try-catch
и тем самым плодить "лишние строчки".
P.S.: По моему скромному мнению — строчки экономить не нужно. И предварительная декларация такого рода переменных лучше, чем по месту.
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
-нотации. Она выглядит вкусно.
Это примерно как возмутиться, что у массивов могут быть повторяющиеся значения, и поэтому рекомендовать всегда вместо него использовать Set.
Моё мнение: они вполне равноправны. Да, где-то let бывает писать удобнее. Но ведь где-то и с var такая же ситуация! (а вот теперь утверждаю, да: всплытие на уровне функции действительно бывает удобным)
всплытие на уровне функции действительно бывает удобным
Эта тема немного холиварная. Большинство из тех, кто не использует var
, считают, что "удобство" в ущерб очевидности, однозначности, понятности кода — скорее зло. В случае var
едва ли оно обоснованное. До того как let
и const
в языке появились, это была частая причина поливать JS грязью.
Кстати, в PHP вообще даже var
писать не нужно. Просто пишешь $mySuperVariable =
и радуешься жизни. "Удобно" :)
Моё имхо насчёт использования следующее:
1. const для любых констант, которые вряд ли может понадобиться менять. Например, число пи. Не могу представить адекватной ситуации, когда бы мне захотелось изменить значение этой константы.
Но при этом не использовать для всех-всех значений, которые не меняются конкретно в этом коде (мы можем что-нибудь захотеть изменить в коде, и нам придётся менять определение переменной).
2. let — когда видимость внутри блока существенно важна. Например, в цикле for, если внутри setTimeout.
3. var — во всех остальных случаях.
Если это не говнокод на кучке глобальных переменных — то зачем ему может понадобится var?
Для красоты. Чтобы явно обозначить переменную, объявленную на уровне процедуры. Хотя, в принципе, в этом случае использования, никакой разницы с let нет.
Опыт показывает что часто при дебаге чужого кода приходится перелистывать весь код, чтобы понять не изменили ли где-то по пути такую-то переменную. А если начать использовать const — то вы удивитесь, как редко, на самом деле, вам нужны изменяемые переменные кроме как в циклах да в аргументах функций.
Вы удивитесь, но у меня ровно противоположный опыт. Смотреть, не изменилась ли переменная, приходится примерно никогда (да и зачем, если можно исполнить код и вывести её в консоль?), зато почти каждый день приходится лезть выше по коду и исправлять const на let, потому что модифицирую код, и внезапно теперь какую-то переменную нужно менять.
А уж в консоли вообще весело. Объявишь какую-нибудь переменную по привычке через const, а потом чертыхаешься, потому что её понадобилось поменять, а уже никак.
Если это не говнокод на кучке глобальных переменных — то зачем ему может понадобится var?
ться.
Для того, чтобы объявлять переменные. Ещё раз: от замены let на var в коде почти всегда не меняется примерно ничего. Хотя можете привести какой-нибудь пример, когда у вас реальный настоящий код, скажем, от замены var на let, стал в 2 раза чище и короче. Есть такие примеры?
Никаких багов не будет, если вы знаете, как работает var.
Тем, что это конструкция с неочевидным поведением, которая породила множество идиотских задачек в тестах и собеседованиях типа "так никто никогда не пишет, но мы хотим чтоб вы разобрались в этом говнокоде", а так же одно из самых идиотских в истории линтеров правил в jslint.
car={'model': 'повозка'}
modelAndVIN = ({model, engine: {vin}}) => {
console.log(`model: ${model} vin: ${vin}`);
}
modelAndVIN(car)
Uncaught TypeError: Cannot destructure property `vin` of 'undefined' or 'null'.
ES6: полезные советы и неочевидные приёмы