Комментарии 88
Фильтрация уникальных значений
лучше делать Array.from чем spread, он работает быстрее
Длина кэш-массива в циклах
неплохо было бы хоть какието цифры по производительности дать
Шёл 2019-й год.
Мамкины оптимизаторы всё ещё советуют:
- Использовать
for
, а неforEach
; - Выносить подсчёт длины массива в
for
в инициализацию цикла. - Заниматься микрооптимизациями.
Ну серьёзно, даже при итерации по массиву с несколькими тысячами элементов профита почти не будет.
Пруф: https://jsperf.com/for-vs-length
При более большом массиве, разница еще сильнее https://jsperf.com/for-vs-length/21 у меня оптимизированный вариант на 21% медленнее.
UPD: странно, но тот же самый код в консоле того же браузера выдает другие результаты
var array = new Array();
for (var i = 0; i < 100000000; i++) {
array.push(i);
}
console.time('normal');
var dummy = 0;
for (var i = 0; i < array.length; i++) {
dummy += array[i];
}
console.timeEnd('normal');
console.time('optimize');
var dummy = 0;
for (var i = 0, al = array.length; i < al; i++) {
dummy += array[i];
}
console.timeEnd('optimize');
normal: 2212.85888671875ms
optimize: 1148.304931640625ms
почти в два раза "оптимизированый" вариант быстрее обычного. Либо jsperf что-то химичит, либо я что-то не понимаю.
UPD: странно, но тот же самый код в консоле того же браузера выдает другие результаты
Поменяйте тесты местами и получите другой результат. Вот мои
1 прогон
optimize: 1810.764892578125 ms
normal: 1554.541259765625 ms
10 прогонов
optimize: 1668.828857421875 ms
normal: 1600.431884765625 ms
UPD... Не заметил, что это статья 2019 года… и ещё думаю, почему ссылки на тесты не работают..
Немного мамкиной аналитики:
https://tinyurl.com/y3cobd4e — если ваш код соптимизируется, то (1) даст 20 раз разницы, а (2) не даст ничего.
https://tinyurl.com/y3c5pllq — если ваш код не соптимизируется, то (1) даст 2 раза разницы, а (2) — даст 15%
array.map( v => { dummy{#} += v } )
Это еще быстрее
Посыпаю голову пеплом: думал, что чем меньше графа, тем лучше. array.map
самый медленный.
dummy{#}
? Впервые такую вижу.Лол, насчет длины массива это плохой совет, согласен, а вот насчет того, что между фор и форич нет разницы это бред. Во-первых, начнем с того, что фор гибче форича в плане брейка. Форич не брейкнешь просто так. Во-вторых, это функция, это отдельный LE-обьект в виде ее хвоста и прочий кложур. Ну а в третьих, это не микрооптимизация. Это как раз дохрена какая оптимизация. Дело в том, что часто почему то разрабы думают, что если там не ядерная физика под капотом какой-то функции, то вроде бы и все норм, оптимизировать не надо. Только вот одна функция может вызывать другую, а та третью и в конце концов случается, что одна какая-то маленькая функция, внутри которой форич, вызывается милион раз во время скрипта. И как раз если бы там был фор, то, возможно этот миллион работал бы быстрее. Безусловно, фор это императивщина. И не надо ее писать везде или писать нечитаемые циклы. Но, бл*дь, не надо их бояться.
Так может быть их нету в учебниках, потому что это вредные советы, которые ухудшают читаемость кода и не дают никакого реального профита? Во всяком случае, советы из разделов про условные операторы и преобразование типов.
Серьезно, сэкономить 8 байт и несколько секунд на parseInt() или Number(), чтобы потом потратить час, гадая что значит в данном контексте конструкция ~~someVar
.
P.S. Я не знаю, почему хабрапарсер превращает 2 восклицательных знака в 3.
Про хак с ~~
я, наример, не знал, хоть и работаю разработчиком уже 14 лет. Видимо, не в тех командах я работаю :)
Но тут вопрос в другом: зачем вообще использовать эти хаки и ухудшать читабельность кода, если есть полноценные, понятные даже людям незнакомым с JS, конструкции? В чем сакральный смысл выполнять работу минификатора?
С хаками дело такое. В большинстве случаев, согласен — незачем. Но некоторые из них оказываются настолько простыми и удобными, что выстреливают — становятся общеизвестными и де-факто как бы новыми операторами. На уровне соглашений, по типу подчеркивания у приватных свойств или доллара у ссылок на дом-элементы.
Насколько я понимаю эти хаки появились с появлением asm.js и началом компиляции нативного кода в js/wasm. Ребята из мозиллы будучи осведомленными об особенностях своего движка нагенерили хаков типа приведения дробных чисел к целочисленым или использование вычитания вместо сложения — дабы увеличить производительность. А оно еще и в хроме заодно заработало быстрее. С тех пор вот и кочуют все эти штуки по чудо гайдам на 12 шагов.
Во-первых, хаки с ~indexOf, ~~ и |0 действительно удобны когда ты пишешь код на коленке, или тебе нужно например по-быстрому что-то проверить или что-нибудь такое. Вставить в конец |0 гораааздо быстрее и проще, чем писать Math.floor(, затем переносить кусок в конец кода и ставить ). И |0 это одна из самых удобных вещей, что я вообще использую когда пишу код по-быстрому на коленке для себя :)
Это случай, когда над кодом работаешь только ты, и всё в нём понимаешь, хочу заметить.
Во-вторых, на самом деле писать !!value или +value или value|0 вместо Boolean(value) / Number(value) / Math.floor(value) совсем не ухудшает читаемость если ты знаешь, что это. Думаю, что про !!value, +value и value + '' знают примерно все, а вот использовать |0 в каком-то реальном коде естественно содержит много минусов вроде того, что тебе придётся предупреждать всех читающих код, что такие конструкции значат.
Есть такой вариант:
for (let i = array.length - 1; i >= 0; i--){ console.log(i); }
obj && obj.method();
в if он точно такой же. что непонятного?
|| тоже периодически используется как показано в статье.
Однажды, у меня был баг, который я весьма долго отлавливал. В итоге выяснилось, что я вместо примерно следующей конструкции:
if (control.isEnabled()) { ... }
Написал:
if (control.isEnabled) { ... }
Да, к сожалению, даже отсутствие подобных хаков не позволяет избегать таких ошибок. Зато правило strict-boolean-expressions в tslint работает исправно.
const uniqueArray = [...new Set(array)];
Добавлю, например, если вы хотите сделать конструкцию
const array = (new Array(10)).map((_, ind) => ind)
Результат удивит, потому что в итоге будет не [0,1,2,3,4,5,6,7,8,9], а массив из 10-ти эфимерных empty. Чтобы этого не происходило, нужно использовать spread оператор, а еще лучше завернуть это в функцию, вроде
function createArray(length) {
return [...new Array(length)];
}
Натолкнулся, когда захотелось сделать код в очень функциональном стиле зачем-то.
Автоматическое связывание
Долго пытался понять, что это значит. Никто так не говорят, все говорят "привязка контекста".
myMethod = () => {
// This method is bound implicitly!
}
С этим кодом есть несколько проблем. Основная — при наследовании нельзя будет вызвать super.myMethod, а так же функция пересоздается каждый раз при создании экземпляра класса, что тратит время и память. И её нет в прототипе.
К слову, state тоже можно так же инициировать, вне конструктора.
На самом деле читаем документацию: «new Array(n)
создаёт массив без элементов, но с заданной длиной».
Согласен, это не совсем логично, но это особенность.
Решение:
const array = new Array(10).fill(0).map((_, ind) => ind)
Если переменная foo имеет подходящую length, то она и будет возвращена. В противном случае мы получим 0.
const foo = 5;
(foo || []).length; // undefined
const bar = true;
(bar || []).length; // undefined
Омг, если вам кажется, что такой код трудно читать, значит вы код наверное вчера писать начали. Я просто поражаюсь когда какой-то сниппет, едва ли на 1% труднее примеров с MDN начинают поливать грязью, что, мол, такое читать невозможно. Просто фейспалм вызывает такое. Нет, я не говорю, что надо писать сразу обфусцированную хреновину, но, епрст, закешировать длину массива в цикле — это каждому школьнику известно и читается очень просто. Если для вас, конкретно, нет — то это не "нечитаемый код", а малый опыт чтения кода или малый опыт работы с этим языком.
Скорее проблема здесь в том, что эти советы (некоторые из них) бесполезны. Например тот же кеш длины массива — это делают все движки уже на фоне и так. Также про то, что ~~ или другие битовые операции снимают дробную часть. Это верно, но нигде не сказано, что это не то же самое, что Math.floor. Диапазон чисел для Math.floor гораздо шире, чем для операторов и использовать его (тот или иной оператор) можно только тогда, когда на 100% знаешь, что число не будет слишком большим.
12 приемов работы с JavaScript, которых нет в большинстве туториаловНу правильно, так как туториалы обычно стараются не учить плохому :)
А статью лучше переименовать в «12 вредных советов» ну или «Как получить от пи#й от коллег при ревью кода на JS» )
Так, символ знака вопроса? может использоваться для извлечения свойства, только если оно не равно нулю (null).
Нет, не может. В JS нет safe navigation operator. Автор, вы про что? В вопросик умеет CoffeeScript и ангуляр. Больше никто в него не умеет. В TS может быть когда-нибудь добавят, но пока нет.
Можно предложить использовать опциональную цепочку при попытке вернуть свойство глубоко в древовидную структуру. Так, символ знака вопроса? может использоваться для извлечения свойства, только если оно не равно нулю (null).
Перевод просто жесть. Такое ощущение что нейронная сеть ворвалась на хабр и начала компилировать случайные слова и знаки препинания.
в большинстве случаев эффективнее кэшировать длину массива <...> при увеличении размера цикла мы получим значительную экономию времени.
Звучит очень сомнительно, если только этот цикл не четвертого уровня вложенности.
еще obj + "" не всегда возвращает строку.
все это может привести к тому, что прога будет работать 99% времени, но иногда глючить. попробуй найди такую ошибку.
и кстати, насколько я помню, старый добрый parseInt — это самый быстрый (про IE6 не знаю) способ преобразования строки в целое число.
Есть у меня совет, который не пишут в книгах, со времён моего jQuery-программирования.
Не стоило, наверное, на хабре про него писать, но так и быть, держите секретные знания:
Если кусок кода не работает и абсолютно непонятно почему, заверните его в
setTimeout(function(){ /*КОД*/},0);
Очень велика вероятность, что код начнёт работать абсолютно непонятно почему.
если такое спасает от каких-то багов, значит, скорее всего, вызов этого код внутри текущей функции вступает в конфликт с кодом, который остался в текущей функции. Либо схватка за ресурсы/объекты, либо еще круче — код внутри вызова пытается уничтожить объект, в функции которого он сам и вызывается
к примеру, такое бывает при смене экранов в игре
когда в onClose одного экрана прямо вызывается goNextScreen, а тот вызывает onStart следующего экрана — вот эти вот onClose предыдущего экрана и оnStart нового часто схлестываются, то за ресурсы, то просто предыдущий экран не может спокойно умереть. Поэтому лайфсайкл экранов и вообще «умирающих стратегий», которые должны полностью исчезнуть и освободить захват, лучше делать через непрямые вызовы «иди к следующему экрану». Надеюсь, что примерно объяснил ))
Math.floor(123456789012345.1); // 123456789012345
123456789012345.1 | 0; // -2045911175
Упс.
12 приемов работы с JavaScript, которых нет в большинстве туториалов