Изучая любой язык программирования, полезно знать о его особенностях и уметь эффективно использовать языковые конструкции. Хочу поделиться с вами шорткатами для JS. Эти сокращения в некоторых случаях могут облегчить чтение кода, а также существенно уменьшить его количество. Однако следует помнить, что они могут сыграть с вами злую шутку и, если использовать их повсеместно, ваш код перестанет быть читаемым и поддерживаемым.
Использование while вместо for
Начнем с простого и достаточно часто встречающегося совета — использовать while вместо for.
var count = foo.length
while (count--) {
// whatever
}
var count = foo.length
for(var i = 0; i < count; i++) {
// whatever
}
Конечно, мне тоже нравится использовать forEach и map, однако в случаях, когда это неуместно, while выглядит более органично и читаемо. К слову, применение переменной для хранения длины массива более производительно, чем вычисление этого значения в каждой итерации.
Использование for-in с обычными массивами
Традиционно конструкцию for-in используют для итерации объектов, но не нужно забывать, что ею можно воспользоваться и для обычного массива. И хотя в документации указано, что for-in не гарантирует консистентность перебора, на практике я не встречал подобной ситуации.
var count = foo.length
for(var i = 0; i < count; i++) {
// whatever foo[i]
}
for(var i in foo) {
// whatever foo[i]
}
Приведение к булеву типу
Иногда требуется привести значение к булеву типу, и я покажу традиционное решение этой задачи, а также ее короткую форму:
var foo = Boolean(bar); var foo = !!bar;
Двойное отрицание работает так же, как и приведение, однако справедливо заметить, что, как и в случае со всеми остальными шорткатами, у новичков (да и не только) могут возникнуть сложности в восприятии такого кода.
Эквивалент Math.floor();
Есть очень простой способ округлить любое число до ближайшего целого:
var intNum = Math.floor(anyNum); var intNum = ~~anyNum;
Двойное побитовое отрицание работает как Math.floor() для положительных чисел и как Math.ceil() для отрицательных. Объяснение, как это работает, можно найти тут. Читать сложнее, но экономит десять символов.
Шорткаты для true и false
Даже для true и false можно сделать шорткат. Это выглядит уже совсем за гранью добра и зла, но раз уж мы начали эту тему, то почему бы и нет:
var foo = true,
bar = false;
var foo = !0,
bar = !1;
Но это, как ни крути, скорее антишорткат, потому как совершенно нечитаем, требует вычисления со стороны JS, да и вообще выглядит обескураживающе.
Условный оператор
Наверняка все знакомы с тернарным оператором, но не все знают, насколько удобным может быть его использование.
if (condition) {
foo = bar;
} else {
foo = baz;
}
foo = (condition ? bar : baz);
Уже неплохо, но можно пойти и дальше и использовать его в несколько строк:
if (age > 18) {
alert("OK, you can go.");
location.assign("continue.html");
} else {
location.assign("backToSchool.html");
}
age > 18 ? (
alert("OK, you can go."),
location.assign("continue.html")
) : (
location.assign("backToSchool.html");
);
Тут есть небольшой нюанс: нужно помнить, что операторы в этом случае должны быть разделены запятой, а не точкой с запятой. Другая замечательная особенность этого оператора — он возвращает значение (это было видно из первого примера), а значит, его можно использовать и с функциями.
if (condition) {
function1();
} else {
function2();
}
(condition ? function1 : function2)();
Использование AND
Не все помнят, как работает AND, — сначала он вычисляет первое значение и, только если оно равно true, вычисляет другое. Посмотрим, как это можно применить.
if (foo) {
bar();
}
if (foo) bar();
if (foo) bar();
foo && bar();
Вкупе с OR можно добиться полного эквивалента if-else, но это не стоит того, во что превратится ваш код.
Приведение строки к числу
var foo = parseFloat('3133.7'),
bar = parseInt('31337');
Короткая форма этой записи достаточно проста и элегантна:
var foo = +'3133.7',
bar = +'31337';
Для обратной задачи тоже есть свой шорткат.
Приведение числа к строке
Тут можно продемонстрировать несколько примеров, как это сделать:
var foo = 3.14,
bar = foo.toString(10); // '3.14'
var foo = 3.14,
bar = foo + ''; // '3.14'
foo += ''; // '3.14'
Я неспроста вызвал метод toString с параметром 10 — он указывает, что число нужно преобразовать в строку в десятичной системе счисления. Соответственно, для случаев, когда вам нужно получить строку в шестнадцатеричной системе счисления, эти шорткаты не помогут.
Проверка результатов indexOf
Иногда для проверки наличия или отсутствия элемента в массиве пишут вот такой код:
if (someArray.indexOf(someElement) >= 0) {
// whatever
}
if (someArray.indexOf(someElement) === -1) {
// whatever
}
Его можно сократить до такого:
if (~someArray.indexOf(someElement)) {
// whatever
}
if (!~someArray.indexOf(someElement)) {
// whatever
}
Чтобы улучшить читабельность этого трюка, проверку можно присвоить переменной с подходящим названием:
var isFound = ~someArray.indexOf(someElement);
if (isFound) {
// whatever
}
Конвертация arguments в массив
Для нормальной работы с arguments в JS не хватает инструментария, поэтому потребность преобразовать arguments к обычному массиву возникает часто. И первое, что удастся загуглить, будет
argsArray = Array.prototype.slice.call(arguments);
Этот вызов можно сократить до
argsArray = [].slice.call(arguments);
Ведь у экземпляра массива тоже есть доступ к прототипу, что сэкономит нам аж 13 символов.
Проверка на null, undefined и empty
Про этот трюк слышали многие, приведу его для тех, кто не знал:
if (foo !== null || foo !== undefined || foo !== '') {
bar = foo;
}
bar = foo || '';
Выводы
Хотелось бы обратить внимание еще раз, что эти примеры мы рассмотрели не для того, чтобы применять их повсеместно, а лишь для того, чтобы лучше понимать возможности некоторых конструкций в JS.
Комментарии (45)
3GinGer
18.12.2015 11:05+11if (foo !== null || foo !== undefined || foo !== '') { bar = foo; } bar = foo || '';
на последний пример стоит обратить внимание, подводный камень при использование числовых данных – 0 попадет под замену
BlessMaster
18.12.2015 11:13+8Без хорошего опыта в работе с JavaScript, лучше всё-таки не изобретать грабли на собственную задницу. И это совет не только новичкам.
foo = 0; foo = false; if (foo !== null || foo !== undefined || foo !== '') { bar = foo; } bar = foo || '';
BlessMaster
18.12.2015 11:20+3«За деревьями не видно леса» (с)
Это выражение всегда истинно:
foo !== null || foo !== undefined || foo !== ''
Стоит немного подкорректировать статью
token
18.12.2015 12:02Добавлю свой, вместо того чтобы делать бесконечный цикл с использованием while:
while (true) { // body }
Можно воспользоваться циклом for:
for (;;) { // body }
Суть в том, что при каждой итерации цикла while условие должно вычисляться (на самом деле этого конечно не происходит благодаря оптимизациям), но я предпочитаю именно for (;;)
wheercool
18.12.2015 12:04+7Приведение строки к числу
var foo = parseFloat('3133.7'), bar = parseInt('31337'); var foo = +'3133.7', bar = +'31337';
parseInt и + работают не одинаково. + более строгая операция, parseInt же позволяет парсить вот такие строки «123abc».
К слову сказать Вы не правильно используете parseInt. Правильно вызывать
parseInt(str, 10);
иначе без указания основания могут возникать неожиданности.
Попробуйте например parseInt('0x93')freakru
19.12.2015 13:02parseInt(str, 10); актуально только для старых браузеров. Проблема была не в вашем примере parseInt('0x93'), ведь вы же явно указали '0х' — использовать восьмеричную систему. Проблема была в ведущем 0, parseInt('093'). А это все вменяемые браузеры обрабатывают корректно.
А если вашем примере использовать основание, как вы предлагаете — parseInt('0x93', 10), получится вообще 0.
auine
18.12.2015 12:20if (foo !== null || foo !== undefined || foo !== '') { bar = foo; } bar = foo || '';
По-джедайски на undefined лучше проверять так:
if(val !== void 0)
В некоторых реализациях ES имеется возможность переопределить значение undefined
Так же строку необходимо «тримать»:
if(val.trim().length)
some_x
18.12.2015 14:51+3Кто в здравом уме будет заменять undefined? Разве чтоб намеренно сломать другие скрипты.
Вы предполагаете что в вашей кодовой базе есть сторонние скрипты которые были умышленно написаны так чтобы сломать другие?
А вообще вместо foo !== null || foo !== undefined можно использовать foo != null
Ununtrium
18.12.2015 15:47+2Давайте не надо. В молодости javascript был редкостным г-ном, хватит уже тащить все это в современную разработку. Не использовать strict mode можно только если вы мазохист.
AndersonDunai
18.12.2015 13:24-1Согласен с использованием вещей типа замены for на while, if на оператор && и некоторых других, т. к. они действительно повышают читабельность кода для других разработчиков.
А вот вещи типа !0, !1 и тернарный оператор для множества выражений я бы избегал. Если вам важен размер выходного кода — тут уже лучше написать на пару строк больше, чтобы сделать код недвусмысленным и оставить эту грязную работу минификаьорам.
overmes
18.12.2015 13:26+7Цените легкость чтения кода выше, чем удобство его написания Даже во время первоначальной разработки программы код приходится читать гораздо чаще, чем писать. Выгода от подхода, повышающего удобство написания кода за счет легкости его чтения, обманчива.
Макконнелл
missingdays
18.12.2015 13:28Итерированние по массиву используя for..in ведёт к тому, что вы итерурете по всем его ключам, а не только индексам.
(var a =[1]; a.foo=«bar»; и тут for..in проитерирует в том числе и по foo). Возможно это плохо, но к примеру, d3.select возвращает массив, на который набиндены всякие методы. И for..in с радостью по ним продётся.
eme
18.12.2015 13:46Я бы назвал большую часть из приведенных примеров сборником «bad practices» исключая разьве что arguments и indexOf
Sirion
18.12.2015 14:02Ну, часть изложенного здесь — вполне устоявшиеся идиомы языка. Хотя должен отметить, что другая часть — ад и мрак.
Starche
18.12.2015 17:06-2indexOf тот ещё bad practice. Приблизительно половина моих знакомых путается, когда ~, а когда !~
Никому не рекомендую использовать, т.к. читать код с этой штуковиной тяжко.
arusakov
18.12.2015 14:10+10Кое-что имеет смысл, кое-что исключительно вредные советы:
Про for-in по массиву уже писали выше — просто не делайте так никогда.
Math.floor и ~~ — это не одно и тоже. Простой пример:
Math.floor(100000000000000.1) 100000000000000 ~~100000000000000.1 276447232
Wat? Тильда — побитовая операция, она приводит операнд к 32 битному целому числу, и уже потом инвертирует биты. Так что используйте тильду только тогда, когда вы точно знаете, что вы делаете.
Slice и arguments — это очень плохие соседи с точки зрения V8. Для того, чтобы ваш код был оптимизирован, с arguments можно делать ровно 3 действия (см. @jsunderhood от Вячеслава Егорова):
- arguments[i] — взятие аргумента по индексу
- arguments.length
- f.apply(x, arguments), где f.apply === Function.prototype.apply
Так что это, к сожалению, распространенный антипаттерн. А самое производительное решение с точки зрение V8 — копировать из arguments в новый массив в ручную, вот таким образом:
var args = new Array(arguments.length); for (var i = 0; i < args.length; ++i) { args[i] = arguments[i]; }
token
18.12.2015 14:23-1Тильда здесь не работает потому, что битовые операторы в JavaScript работают лишь с 32-битными значениями.
RubaXa
18.12.2015 14:25+3«распространенный антипаттерн» — но всё же в это надо упереться для начала. А ещё на V8 свет клином не сошелся и оптимизируя под него, можно просесть в остальных браузерах. И главное, сам V8 может измениться, вспомни свои пост про sort, вот и это всё может поменяться. Так что Array.from мой выбор :]
arusakov
18.12.2015 14:35Кстати, реально редко использую Array.from, совсем про него позабыл. Спасибо, что напомнил)
А насчет V8. Забывать про других не стоит, но все чаще возникают ситуации, когда js и его реализация в V8 начинают быть не отделимы (по крайней мере сейчас): V8 самостоятельно используется в большом количестве различных проектов, Node.js, Electron, NW.js, и, конечно, Chrome OS, Extensions and Apps — это целый мир =)RubaXa
18.12.2015 14:41Так да и это печально на самом деле, но если уже твой путь лежит через V8, то тут уже привет IRHydra ;]
arusakov
18.12.2015 14:48Согласись, что если бы существовала одна единственная реализация js — то разработчикам жилось бы пусть менее весло, но значительно проще. Возможно, язык бы не так стремительно развивался, но это уже немного из другой области вопрос =)
RubaXa
18.12.2015 14:54Да, истинная кроссбраузерность — это настоящий вызов :] Но даже если бы был один V8, остаются баги в нем, например один из моих любимых: code.google.com/p/chromium/issues/detail?id=136356
jrip
18.12.2015 14:39+1За всякие "!!bar; ~~anyNum;condition? bar: baz; ~someArray", особенно в составе большого выражения, последующие разработчики могут и покарать.
Вообще странное желание загадить код, лишь бы сократить его размер. Мнимайзер + сжатие и не надо срать себе же в мозг.
KhodeN
19.12.2015 03:16Полезно для разработчиков минимизаторов, типа Google Closure или UglifyJS.
Явное лучше неявного.
и!!b
Boolean(b)
Второй вариант, хоть и длинее, но сильно понятнее и читаемей.
Не нужно хакать язык, синтаксические конструкции лучше использовать по назначению.
Код обычно пишется не для конкурса/олимпиады, где нужно показать, что ты знаешь все ньюансы языка. Он пишется для людей.
bo883
19.12.2015 22:33Мне кажется прежде чем называть что то «bad practice»(одни говорят что это плохо читается, другие не понимают) может проблема просто в том что не хотят или не знают должным образом язык. Ведь все относительно, посмотрите на хаскель, кому то покажет он просто адовым, я к тому что если ты знаешь, то уже не кажется что то непонятным.
VolCh
21.12.2015 10:22+1Не должно быть в обычном (не оптимизированном) коде конструкций, для понимания которых нужно держать в голове всю спецификацию языка на сотни страниц. А оптимизированный код должен быть прокомментирован типа
var intNum = ~~anyNum; // отбрасываем дробную часть и урезаем целую до 32 бит — по ТЗ больше 32 бит быть не может
Riim
21.12.2015 13:34+1Меня одного напрягает, что в конструкциях типа:
var i = list.length; while (i--) { var item = list[i]; // ... }
в последней итерации произойдёт лишний декремент?
Иногда он конечно нужен, но чаще лучше без него:
var i = list.length; while (i) { var item = list[--i]; // ... }
Понятно, что это не даст какого-то улучшения производительности, но так оно как-то правильней что ли.
Ну и раз уж `i` нужна только для цикла, то в идеале вообще так:
for (var i = list.length; i;) { var item = list[--i]; // ... }
poxu
21.12.2015 14:59Вот из-за таких моментов я вообще предпочитаю не использовать оператор декремента в условии и оператор декремента при указании индекса массива, и честно говоря оператор декремента вообще. Да и неявное приведение типов в условии меня раздражает. И объявление переменных внутри цикла — увы наглая ложь :(.
То есть я конструкции наподобие приведённой выше пишу примерно следующим образом.
var i = list.length; var item; while(i >= 0) { i -= 1; item = list[i]; }
Потому, что сегодня ты используешь оператор декремента, завтра вставишь его туда, где должно стоять определение индекса массива и перепутаешь --i с i--, а послезавтра откроешь для себя недокументированный оператор downto ( --> ) и начнёшь писать ересь наподобие:
for(var i = 10; i --> 0; console.log(i));
А кончится всё тем, что ты решишь, что раз после if только одна строчка, то и скобки фигурные можно не ставить.
Нуегонахрен!
Jeditobe
21.12.2015 14:52-1Уважаемая компания Майл.ру
Можете мне прокомметировать это письмо по конкурсу «Каникулы в Калифорнии»:
В первом этапе конкурса «Каникулы в Калифорнии» вы ответили верно не на все вопросы. Но это не повод расстраиваться! Уверены, в следующий раз вам повезет!
Следите за нашими новостями!
Что это значит?
Т.е. я недостаточно точно угадал слово Паскаль в видео ролике, которое написано открытым текстом?
Или я не смог найти куски картинки, закомментированные в коде главных страничек ваших сайтов?
it.mail.ru/files/23_1n.jpg
it.mail.ru/files/24_2n.jpg
it.mail.ru/files/25_3n.jpg
it.mail.ru/files/26_4n.jpg
it.mail.ru/files/27_5n.jpg
it.mail.ru/files/28_6n.jpg
it.mail.ru/files/29_7n.jpg
it.mail.ru/files/30_8n.jpg
Вот эти? Вы думаете, я мог ошибиться, если даже они пронумерованы не рандомно, а по порядку?
Простите, но этот конкурс больше похож на лохотрон.pkruglov
21.12.2015 18:02Привет! Вам ответили в переписке, что были неправильно высланы ссылки на куски картинки (одна ссылка была дублирована, а одна отсутствовала). Проверка происходит в автоматическом режиме, поэтому результаты изменить не представляется возможным, даже если ошибка была допущена по случайности.
Jeditobe
21.12.2015 20:59+1Опять же камень в огород тех, кто разрабатывал конкурс. Я нашел все коды, просто какой-то из них был случайно введен дважды. Там нельзя было просмотреть уже введенные коды, при том, что картинка все равно «собиралась».
И вы уверены, что конкурс для настоящих профессионалов должен быть таким?
KIlLXXXVI
21.12.2015 19:12Пишите код для людей! Для машин есть аглифаеры! Не делайте из коллеги компилятор и вам будет меньше икаться.
Nookie-Grey
21.12.2015 22:10бррр… Жесть. последний тэг как нельзя лучше подходит к этой теме.
По делу, кто может подробно объяснить с примерами и сутью:
Array.apply(null, {length: N}).map(Function.call, Math.random) Array.apply(null, {length: N}).map(Number.call, Number)
И его реализация для преобразования строки в массив Char-кодов (иммею ввиду String.charCodeAt())
Моя реализацияArray.apply('', Array(str.length)).map(Function.prototype.call, ''.charCodeAt.bind(str)));
В приципе я понимаю кое-как, но хотелось бы подробно, полностью и точно.
deivan
Поэтому для цикла for обычно используется паттерн
и получается не менее производительно чем while
Aniro
Это древняя легенда, уже многие годы нет значимой разницы между i < arr.length, i < length, и while(length--)
оптимизаторы умеют это сами абсолютно везде:
jsperf.com/array-length-vs-cached/47
А вот использование for-in вместо for это жесть просто. for-in можно использовать только как итератор по полям объекта, и нельзя для перебора объектов в большой коллекции. Разве что на производительность вообще наплевать:
jsperf.com/for-loop-vs-for-in-loop/48
poxu
Ну вообще даже для итерации по полям объекта for… in использовать не надо. В javascript давно уже есть Object.keys().
Если хотим перебрать все свойства объекта делаем так:
Проверки на hasOwnProperty не нужны, Object.keys() это уже сделал.
RubaXa
И не только это, Object.keys возвращает ключи V8 как интернализованные строки, что позволяет ему использовать быстро получить значение по такому ключу.
ComodoHacker
К слову, во втором тесте используется описанный в статье шорткат для if. :)