Как стать автором
Обновить

Комментарии 88

Фильтрация уникальных значений

лучше делать Array.from чем spread, он работает быстрее

Длина кэш-массива в циклах

неплохо было бы хоть какието цифры по производительности дать
«лучше делать Array.from чем spread, он работает быстрее» — нет
НЛО прилетело и опубликовало эту надпись здесь
Библиотека uniq модифицирует исходный массив, а подход, set+spread создает новый массив. Это совсем не одно и то же, могут быть задачи, где решение uniq не подойдет.
Окей, есть underscore для таких вещей
а их уже нет) в хроме без кеширования вроде даже быстрее работает)))
неплохо было бы хоть какието цифры по производительности дать

Цифры будут зависеть от интерпретатора, его версии, размера массива. Вот тут как раз оценивают эту задачу, но для свежего хрома [...Set]/Array.from(Set) уже самый быстрый.

Шёл 2019-й год.
Мамкины оптимизаторы всё ещё советуют:


  1. Использовать for, а не forEach;
  2. Выносить подсчёт длины массива в for в инициализацию цикла.
  3. Заниматься микрооптимизациями.

Ну серьёзно, даже при итерации по массиву с несколькими тысячами элементов профита почти не будет.
Пруф: https://jsperf.com/for-vs-length

Мой Chrome показал, что optimized loop *медленнее* на 2% =)
НЛО прилетело и опубликовало эту надпись здесь
Подскажите пожалуйста каким образом просмотреть такой байт код для javascript?
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

При более большом массиве, разница еще сильнее 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 что-то химичит, либо я что-то не понимаю.

чтобы функция была обработана JIT, ее нужно вызвать несколько раз. а у вас код в консоли только 1 раз запускается.
вот тут, кстати, тонкий момент.
порой важно знать что шустрее без JIT — есть масса функций которые будут вызваны всего 1-2 раза
UPD: странно, но тот же самый код в консоле того же браузера выдает другие результаты

Поменяйте тесты местами и получите другой результат. Вот мои
1 прогон
optimize: 1810.764892578125 ms
normal: 1554.541259765625 ms


10 прогонов
optimize: 1668.828857421875 ms
normal: 1600.431884765625 ms


UPD... Не заметил, что это статья 2019 года… и ещё думаю, почему ссылки на тесты не работают..

Ну, на счет for vs forEach я бы не был так категоричен.

Немного мамкиной аналитики:


https://tinyurl.com/y3cobd4e — если ваш код соптимизируется, то (1) даст 20 раз разницы, а (2) не даст ничего.


https://tinyurl.com/y3c5pllq — если ваш код не соптимизируется, то (1) даст 2 раза разницы, а (2) — даст 15%

array.map( v => { dummy{#} += v } )

Это еще быстрее

Да нет, медленнее: tinyurl.com/yyeghea9
Ну вот каким образом Array.map может быть быстрее, чем Array.forEach?! Хотя бы даже теоретически?

Посыпаю голову пеплом: думал, что чем меньше графа, тем лучше. array.map самый медленный.

А что это вообще за конструкция — dummy{#}? Впервые такую вижу.
Или какие-то литералы с нового стандарта или шаблонизатор какой-то на сайте работает.
Да, обычный плейсхолдер. Подставляет номер итерации. Нужен так как код копипастится много раз в одном и том же скоупе.
Кнопку New benchmark нужно сделать более явной — я был искоенне уверен, что там написано "+ Optimized" (обычная плюс оптимизированная версии).
Там же расстояние большое между ними.
Сам плюс очень мелкий, он в принципе не воспринимается как кнопка. Что мешает сделать вместо иконок нормальные кнопки с текстом? Там же места навалом.
Да впринципе ничего, так и сделаю.
А вот почему нет?! Хуже то от этого не будет точно.

Лол, насчет длины массива это плохой совет, согласен, а вот насчет того, что между фор и форич нет разницы это бред. Во-первых, начнем с того, что фор гибче форича в плане брейка. Форич не брейкнешь просто так. Во-вторых, это функция, это отдельный LE-обьект в виде ее хвоста и прочий кложур. Ну а в третьих, это не микрооптимизация. Это как раз дохрена какая оптимизация. Дело в том, что часто почему то разрабы думают, что если там не ядерная физика под капотом какой-то функции, то вроде бы и все норм, оптимизировать не надо. Только вот одна функция может вызывать другую, а та третью и в конце концов случается, что одна какая-то маленькая функция, внутри которой форич, вызывается милион раз во время скрипта. И как раз если бы там был фор, то, возможно этот миллион работал бы быстрее. Безусловно, фор это императивщина. И не надо ее писать везде или писать нечитаемые циклы. Но, бл*дь, не надо их бояться.

Вместо forEach можно юзать some и every, они брейкаются возвращением true / false. Но в остальном согласен.
Половина триксов из разряда «я поинтерисовался как это делают нормальные программисты»
А другая половина из разряда «никогда так не делайте» :)

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


Серьезно, сэкономить 8 байт и несколько секунд на parseInt() или Number(), чтобы потом потратить час, гадая что значит в данном контексте конструкция ~~someVar.

НЛО прилетело и опубликовало эту надпись здесь
Да уж, код с такими оптимизациями превращается в худшие примеры на Perl.
!!! и ~~ настолько широко распространены, что уже давно перешли в разряд де-факто узаконенных хаков. Любой JS-программист видел их бесчетное количество раз и никогда не гадает над значением, оно общеизвестно.

P.S. Я не знаю, почему хабрапарсер превращает 2 восклицательных знака в 3.

Про хак с ~~ я, наример, не знал, хоть и работаю разработчиком уже 14 лет. Видимо, не в тех командах я работаю :)


Но тут вопрос в другом: зачем вообще использовать эти хаки и ухудшать читабельность кода, если есть полноценные, понятные даже людям незнакомым с JS, конструкции? В чем сакральный смысл выполнять работу минификатора?

У меня в голове не укладывается, как можно было за 14 лет его не узнать. Он используется в чертовой прорве статей, библиотек и проектов. Разве что, сидя в подвале, пилить один проект и не высовывать носа на гитхаб.

С хаками дело такое. В большинстве случаев, согласен — незачем. Но некоторые из них оказываются настолько простыми и удобными, что выстреливают — становятся общеизвестными и де-факто как бы новыми операторами. На уровне соглашений, по типу подчеркивания у приватных свойств или доллара у ссылок на дом-элементы.

Насколько я понимаю эти хаки появились с появлением 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 в каком-то реальном коде естественно содержит много минусов вроде того, что тебе придётся предупреждать всех читающих код, что такие конструкции значат.
О, я такое использую для splice (удаление из массива) по определенному условию, например. Иначе в нормальном порядке может получится так, что очередной индекс не существует. Хотя сомневаюсь, все равно, что это хорошая мысль. Может быть лучше использовать reduce?
Хорошая рекомендация использовать понятный простой код без «финтиплюшек». Думайте о тех кто будет смотреть твой код после тебя ;)
а чем плох код
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 тоже можно так же инициировать, вне конструктора.

Здесь все примеры имеют либо неочевидные сайд-эффекты, либо сомнительную эффективность, либо плохую читаемость.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
или const array = (new Array(10)).fill(какое-нибудь значение).map((_, ind) => ind)

На самом деле читаем документацию: «new Array(n) создаёт массив без элементов, но с заданной длиной».
Согласен, это не совсем логично, но это особенность.


Решение:
const array = new Array(10).fill(0).map((_, ind) => ind)

Тут особенность не new Array(n) а map, который не вызывает колбэк для не заданных индексов, если написать обычный for все будет норм.
Пожалуйста ни в коем случае не показывайте это начинающим разработчикам!
Если переменная foo имеет подходящую length, то она и будет возвращена. В противном случае мы получим 0.
const foo = 5;
(foo || []).length; // undefined

const bar = true;
(bar || []).length; // undefined

Ну и сюда же


const bar = function () {};
(bar || [1, 2, 3]).length; // 0
НЛО прилетело и опубликовало эту надпись здесь

Омг, если вам кажется, что такой код трудно читать, значит вы код наверное вчера писать начали. Я просто поражаюсь когда какой-то сниппет, едва ли на 1% труднее примеров с MDN начинают поливать грязью, что, мол, такое читать невозможно. Просто фейспалм вызывает такое. Нет, я не говорю, что надо писать сразу обфусцированную хреновину, но, епрст, закешировать длину массива в цикле — это каждому школьнику известно и читается очень просто. Если для вас, конкретно, нет — то это не "нечитаемый код", а малый опыт чтения кода или малый опыт работы с этим языком.

Скорее проблема здесь в том, что эти советы (некоторые из них) бесполезны. Например тот же кеш длины массива — это делают все движки уже на фоне и так. Также про то, что ~~ или другие битовые операции снимают дробную часть. Это верно, но нигде не сказано, что это не то же самое, что Math.floor. Диапазон чисел для Math.floor гораздо шире, чем для операторов и использовать его (тот или иной оператор) можно только тогда, когда на 100% знаешь, что число не будет слишком большим.

кеш длины массива — это делают все движки уже на фоне и так
Если JIT решил, что функция горячая и поддается оптимизации — да. Если нет, то извините.
12 приемов работы с JavaScript, которых нет в большинстве туториалов
Ну правильно, так как туториалы обычно стараются не учить плохому :)

А статью лучше переименовать в «12 вредных советов» ну или «Как получить от пи#й от коллег при ревью кода на JS» )
Так, символ знака вопроса? может использоваться для извлечения свойства, только если оно не равно нулю (null).

Нет, не может. В JS нет safe navigation operator. Автор, вы про что? В вопросик умеет CoffeeScript и ангуляр. Больше никто в него не умеет. В TS может быть когда-нибудь добавят, но пока нет.
Любопытно — я был чуть ли единственным, кто негативно откомментировал эту статью на медиуме. В основном были восторги. То ли уровень разработчиков на хабре выше, чем на медиуме, то ли там просто не любят негатив писать.
Просто разные парадигмы.
У них: «Вау! Ну и фрик! Круто! Спляши еще!» (пусть расцветают все уродские цветы)
У нас: «Ну и дебил! Кто ж так делает?! Убейся.» (делай правильно, будь как все (какими все хотели бы быть, но, разумеется, не всем дано))
Можно предложить использовать опциональную цепочку при попытке вернуть свойство глубоко в древовидную структуру. Так, символ знака вопроса? может использоваться для извлечения свойства, только если оно не равно нулю (null).

Перевод просто жесть. Такое ощущение что нейронная сеть ворвалась на хабр и начала компилировать случайные слова и знаки препинания.


в большинстве случаев эффективнее кэшировать длину массива <...> при увеличении размера цикла мы получим значительную экономию времени.

Звучит очень сомнительно, если только этот цикл не четвертого уровня вложенности.

автор почему-то не упомянул, что все эти выпендрежные побитовые операторы ~|& усекают число до 32-битного целого.

еще obj + "" не всегда возвращает строку.

все это может привести к тому, что прога будет работать 99% времени, но иногда глючить. попробуй найди такую ошибку.

и кстати, насколько я помню, старый добрый parseInt — это самый быстрый (про IE6 не знаю) способ преобразования строки в целое число.
>еще obj + "" не всегда возвращает строку.
можно пример пожалуйста?

Формально "не строка" будет, если у объекта определить метод toString, возвращающий не string. Но по-дефолту вы получите не менее бесполезный [object Object]

$ node
> ({toString: () => 22}) + ""
'22'
Это все еще строка
({toString: () => []}) + ''

Кинет ошибку
я немного не так выразился. результатом будет строка, но...

Есть у меня совет, который не пишут в книгах, со времён моего jQuery-программирования.
Не стоило, наверное, на хабре про него писать, но так и быть, держите секретные знания:
Если кусок кода не работает и абсолютно непонятно почему, заверните его в


setTimeout(function(){ /*КОД*/},0);

Очень велика вероятность, что код начнёт работать абсолютно непонятно почему.

код через setTimeout будет вызван не внутри стека текущей функции, а сам по себе в следующем свободном интервале call stack

если такое спасает от каких-то багов, значит, скорее всего, вызов этого код внутри текущей функции вступает в конфликт с кодом, который остался в текущей функции. Либо схватка за ресурсы/объекты, либо еще круче — код внутри вызова пытается уничтожить объект, в функции которого он сам и вызывается

к примеру, такое бывает при смене экранов в игре
когда в onClose одного экрана прямо вызывается goNextScreen, а тот вызывает onStart следующего экрана — вот эти вот onClose предыдущего экрана и оnStart нового часто схлестываются, то за ресурсы, то просто предыдущий экран не может спокойно умереть. Поэтому лайфсайкл экранов и вообще «умирающих стратегий», которые должны полностью исчезнуть и освободить захват, лучше делать через непрямые вызовы «иди к следующему экрану». Надеюсь, что примерно объяснил ))

А потом так же неожиданно бросит. Например, при показе.
Нуб, не делай так. Некоторые «хаки» могут соптимизировать тебя из конторы.
Большинство этих приемов в реальных проектах использовать не следует. Один из признаков хорошего кода это его читаемость и понятность «типовому / среднему» разработчику.
Стыдно должно быть такое переводить
Float в целое число
Math.floor(123456789012345.1);  // 123456789012345
123456789012345.1 | 0;  // -2045911175

Упс.

А это и не float, а вполне себе double.


float x = 123456789012345.1;
double y = 123456789012345.1;
printf("float: %.2f double: %.2f\n",x,y);

на выводе дает


float: 123456788103168.00 double: 123456789012345.09

https://ideone.com/hBfyVr

я сперва усиленно педалировал стрелочные для байдинга к инстансу. а теперь вот могу сказать: если вам важна отладка вашего кода в хром девтулс, не делайте так. при использовании сорсмаппов хром не понимает стрелочные функции и говорит что this не существует, а следовательно typeerror тебе а не значения в инстансе. в том же stage0 есть новый bind syntax :: — он куда лучше, так как транслируется в обычный bind. другой подход это определение методов в конструторе или использование замыканий вместо this… и еще — это не такой уж уникальный список — половина описанного есть в учебниках по es5 — остальное в учебниках по еs6+, а некоторые его пунткты вроде замены ветвления на или — отстой. в программировании число символов не главное, главное чтобы всем было понятно, что ты делаешь и зачем, а для минификации есть инструменты.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий