Изучая любой язык программирования, полезно знать о его особенностях и уметь эффективно использовать языковые конструкции. Хочу поделиться с вами шорткатами для 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)


  1. deivan
    18.12.2015 11:03
    +8

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

    Поэтому для цикла for обычно используется паттерн
    for(var i=0, l = arr.length; i<l ; i++)
    

    и получается не менее производительно чем while


    1. Aniro
      18.12.2015 12:10
      +17

      Это древняя легенда, уже многие годы нет значимой разницы между 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


      1. poxu
        18.12.2015 14:07
        +4

        Ну вообще даже для итерации по полям объекта for… in использовать не надо. В javascript давно уже есть Object.keys().

        Если хотим перебрать все свойства объекта делаем так:

        var map = {
            'first': 'firstVal',
            'second' : 'secondVal'
        };
        
        var fields = Object.keys(map);
        
        fields.forEach(function(field) {
            console.log(map[field]);
        });
        


        Проверки на hasOwnProperty не нужны, Object.keys() это уже сделал.


        1. RubaXa
          18.12.2015 14:20

          И не только это, Object.keys возвращает ключи V8 как интернализованные строки, что позволяет ему использовать быстро получить значение по такому ключу.


      1. ComodoHacker
        22.12.2015 18:25

        К слову, во втором тесте используется описанный в статье шорткат для if. :)


  1. 3GinGer
    18.12.2015 11:05
    +11

    if (foo !== null || foo !== undefined || foo !== '') {
         bar = foo;
    }
    bar = foo || '';
    


    на последний пример стоит обратить внимание, подводный камень при использование числовых данных – 0 попадет под замену


    1. VolCh
      18.12.2015 16:48

      А также NaN для числовых и false для булиня :)


      1. 3GinGer
        18.12.2015 20:23

        числовые факапы самые частые) не раз ловили похожую ситуацию


  1. BlessMaster
    18.12.2015 11:13
    +8

    Без хорошего опыта в работе с JavaScript, лучше всё-таки не изобретать грабли на собственную задницу. И это совет не только новичкам.

    foo = 0;
    foo = false;
    
    if (foo !== null || foo !== undefined || foo !== '') {
         bar = foo;
    }
    
    bar = foo || '';
    


    1. BlessMaster
      18.12.2015 11:20
      +3

      «За деревьями не видно леса» (с)
      Это выражение всегда истинно:

      foo !== null || foo !== undefined || foo !== ''
      

      Стоит немного подкорректировать статью


  1. token
    18.12.2015 12:02

    Добавлю свой, вместо того чтобы делать бесконечный цикл с использованием while:

    while (true) {
       // body
    }
    


    Можно воспользоваться циклом for:

    for (;;) {
       // body
    }
    


    Суть в том, что при каждой итерации цикла while условие должно вычисляться (на самом деле этого конечно не происходит благодаря оптимизациям), но я предпочитаю именно for (;;)


  1. 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')


    1. freakru
      19.12.2015 13:02

      parseInt(str, 10); актуально только для старых браузеров. Проблема была не в вашем примере parseInt('0x93'), ведь вы же явно указали '0х' — использовать восьмеричную систему. Проблема была в ведущем 0, parseInt('093'). А это все вменяемые браузеры обрабатывают корректно.

      А если вашем примере использовать основание, как вы предлагаете — parseInt('0x93', 10), получится вообще 0.


  1. auine
    18.12.2015 12:20

    if (foo !== null || foo !== undefined || foo !== '') {
         bar = foo;
    }
    bar = foo || '';
    


    По-джедайски на undefined лучше проверять так:

    if(val !== void 0)
    

    В некоторых реализациях ES имеется возможность переопределить значение undefined

    Так же строку необходимо «тримать»:

    if(val.trim().length)
    


    1. some_x
      18.12.2015 14:51
      +3

      Кто в здравом уме будет заменять undefined? Разве чтоб намеренно сломать другие скрипты.
      Вы предполагаете что в вашей кодовой базе есть сторонние скрипты которые были умышленно написаны так чтобы сломать другие?

      А вообще вместо foo !== null || foo !== undefined можно использовать foo != null


    1. Ununtrium
      18.12.2015 15:47
      +2

      Давайте не надо. В молодости javascript был редкостным г-ном, хватит уже тащить все это в современную разработку. Не использовать strict mode можно только если вы мазохист.


  1. intenter
    18.12.2015 12:53
    +12

    Читать сложнее, но экономит десять символов.

    А оно того стоит?


    1. kidar2
      18.12.2015 16:15
      +1

      Конечно нет. Тем более что многие сокращения делает обфускатор


  1. AndersonDunai
    18.12.2015 13:24
    -1

    Согласен с использованием вещей типа замены for на while, if на оператор && и некоторых других, т. к. они действительно повышают читабельность кода для других разработчиков.

    А вот вещи типа !0, !1 и тернарный оператор для множества выражений я бы избегал. Если вам важен размер выходного кода — тут уже лучше написать на пару строк больше, чтобы сделать код недвусмысленным и оставить эту грязную работу минификаьорам.


  1. overmes
    18.12.2015 13:26
    +7

    Цените легкость чтения кода выше, чем удобство его написания Даже во время первоначальной разработки программы код приходится читать гораздо чаще, чем писать. Выгода от подхода, повышающего удобство написания кода за счет легкости его чтения, обманчива.

    Макконнелл


  1. missingdays
    18.12.2015 13:28

    Итерированние по массиву используя for..in ведёт к тому, что вы итерурете по всем его ключам, а не только индексам.
    (var a =[1]; a.foo=«bar»; и тут for..in проитерирует в том числе и по foo). Возможно это плохо, но к примеру, d3.select возвращает массив, на который набиндены всякие методы. И for..in с радостью по ним продётся.


  1. eme
    18.12.2015 13:46

    Я бы назвал большую часть из приведенных примеров сборником «bad practices» исключая разьве что arguments и indexOf


    1. Sirion
      18.12.2015 14:02

      Ну, часть изложенного здесь — вполне устоявшиеся идиомы языка. Хотя должен отметить, что другая часть — ад и мрак.


    1. Starche
      18.12.2015 17:06
      -2

      indexOf тот ещё bad practice. Приблизительно половина моих знакомых путается, когда ~, а когда !~
      Никому не рекомендую использовать, т.к. читать код с этой штуковиной тяжко.


  1. 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 от Вячеслава Егорова):
    1. arguments[i] — взятие аргумента по индексу
    2. arguments.length
    3. 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];
    }
    


    1. token
      18.12.2015 14:23
      -1

      Тильда здесь не работает потому, что битовые операторы в JavaScript работают лишь с 32-битными значениями.


    1. RubaXa
      18.12.2015 14:25
      +3

      «распространенный антипаттерн» — но всё же в это надо упереться для начала. А ещё на V8 свет клином не сошелся и оптимизируя под него, можно просесть в остальных браузерах. И главное, сам V8 может измениться, вспомни свои пост про sort, вот и это всё может поменяться. Так что Array.from мой выбор :]


      1. arusakov
        18.12.2015 14:35

        Кстати, реально редко использую Array.from, совсем про него позабыл. Спасибо, что напомнил)
        А насчет V8. Забывать про других не стоит, но все чаще возникают ситуации, когда js и его реализация в V8 начинают быть не отделимы (по крайней мере сейчас): V8 самостоятельно используется в большом количестве различных проектов, Node.js, Electron, NW.js, и, конечно, Chrome OS, Extensions and Apps — это целый мир =)


        1. RubaXa
          18.12.2015 14:41

          Так да и это печально на самом деле, но если уже твой путь лежит через V8, то тут уже привет IRHydra ;]


          1. arusakov
            18.12.2015 14:48

            Согласись, что если бы существовала одна единственная реализация js — то разработчикам жилось бы пусть менее весло, но значительно проще. Возможно, язык бы не так стремительно развивался, но это уже немного из другой области вопрос =)


            1. arusakov
              18.12.2015 14:52

              *менее весело


            1. RubaXa
              18.12.2015 14:54

              Да, истинная кроссбраузерность — это настоящий вызов :] Но даже если бы был один V8, остаются баги в нем, например один из моих любимых: code.google.com/p/chromium/issues/detail?id=136356


        1. RubaXa
          18.12.2015 17:37

          Во! Array.from({length: 10})


  1. jrip
    18.12.2015 14:39
    +1

    За всякие "!!bar; ~~anyNum;condition? bar: baz; ~someArray", особенно в составе большого выражения, последующие разработчики могут и покарать.
    Вообще странное желание загадить код, лишь бы сократить его размер. Мнимайзер + сжатие и не надо срать себе же в мозг.


  1. TheShock
    18.12.2015 16:57
    +15

    Сборник вредных советов.


  1. KhodeN
    19.12.2015 03:16

    Полезно для разработчиков минимизаторов, типа Google Closure или UglifyJS.
    Явное лучше неявного.

    !!b
    
    и
    Boolean(b)
    


    Второй вариант, хоть и длинее, но сильно понятнее и читаемей.

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

    Код обычно пишется не для конкурса/олимпиады, где нужно показать, что ты знаешь все ньюансы языка. Он пишется для людей.


  1. bo883
    19.12.2015 22:33

    Мне кажется прежде чем называть что то «bad practice»(одни говорят что это плохо читается, другие не понимают) может проблема просто в том что не хотят или не знают должным образом язык. Ведь все относительно, посмотрите на хаскель, кому то покажет он просто адовым, я к тому что если ты знаешь, то уже не кажется что то непонятным.


    1. VolCh
      21.12.2015 10:22
      +1

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

      var intNum = ~~anyNum; // отбрасываем дробную часть и урезаем целую до 32 бит — по ТЗ больше 32 бит быть не может


  1. 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];
      // ...
    }
    


    1. 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 только одна строчка, то и скобки фигурные можно не ставить.
      Нуегонахрен!


  1. 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

    Вот эти? Вы думаете, я мог ошибиться, если даже они пронумерованы не рандомно, а по порядку?

    Простите, но этот конкурс больше похож на лохотрон.


    1. pkruglov
      21.12.2015 18:02

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


      1. Jeditobe
        21.12.2015 20:59
        +1

        Опять же камень в огород тех, кто разрабатывал конкурс. Я нашел все коды, просто какой-то из них был случайно введен дважды. Там нельзя было просмотреть уже введенные коды, при том, что картинка все равно «собиралась».

        И вы уверены, что конкурс для настоящих профессионалов должен быть таким?


  1. KIlLXXXVI
    21.12.2015 19:12

    Пишите код для людей! Для машин есть аглифаеры! Не делайте из коллеги компилятор и вам будет меньше икаться.


  1. 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)));
    

    В приципе я понимаю кое-как, но хотелось бы подробно, полностью и точно.