company_banner

Шорткаты в JavaScript



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

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

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

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

      и получается не менее производительно чем while
        +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
          +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() это уже сделал.

            0
            И не только это, Object.keys возвращает ключи V8 как интернализованные строки, что позволяет ему использовать быстро получить значение по такому ключу.
            0
            К слову, во втором тесте используется описанный в статье шорткат для if. :)
          +11
          if (foo !== null || foo !== undefined || foo !== '') {
               bar = foo;
          }
          bar = foo || '';
          


          на последний пример стоит обратить внимание, подводный камень при использование числовых данных – 0 попадет под замену
            0
            А также NaN для числовых и false для булиня :)
              0
              числовые факапы самые частые) не раз ловили похожую ситуацию
            +8
            Без хорошего опыта в работе с JavaScript, лучше всё-таки не изобретать грабли на собственную задницу. И это совет не только новичкам.

            foo = 0;
            foo = false;
            
            if (foo !== null || foo !== undefined || foo !== '') {
                 bar = foo;
            }
            
            bar = foo || '';
            
              +3
              «За деревьями не видно леса» (с)
              Это выражение всегда истинно:

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

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

              while (true) {
                 // body
              }
              


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

              for (;;) {
                 // body
              }
              


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

                var foo = parseFloat('3133.7'),
                    bar = parseInt('31337');
                
                var foo = +'3133.7',
                    bar = +'31337';
                



                parseInt и + работают не одинаково. + более строгая операция, parseInt же позволяет парсить вот такие строки «123abc».
                К слову сказать Вы не правильно используете parseInt. Правильно вызывать
                parseInt(str, 10);
                

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

                  А если вашем примере использовать основание, как вы предлагаете — parseInt('0x93', 10), получится вообще 0.
                  0
                  if (foo !== null || foo !== undefined || foo !== '') {
                       bar = foo;
                  }
                  bar = foo || '';
                  


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

                  if(val !== void 0)
                  

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

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

                  if(val.trim().length)
                  
                    +3
                    Кто в здравом уме будет заменять undefined? Разве чтоб намеренно сломать другие скрипты.
                    Вы предполагаете что в вашей кодовой базе есть сторонние скрипты которые были умышленно написаны так чтобы сломать другие?

                    А вообще вместо foo !== null || foo !== undefined можно использовать foo != null
                      +2
                      Давайте не надо. В молодости javascript был редкостным г-ном, хватит уже тащить все это в современную разработку. Не использовать strict mode можно только если вы мазохист.
                      +12
                      Читать сложнее, но экономит десять символов.

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

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

                          Макконнелл
                            0
                            Итерированние по массиву используя for..in ведёт к тому, что вы итерурете по всем его ключам, а не только индексам.
                            (var a =[1]; a.foo=«bar»; и тут for..in проитерирует в том числе и по foo). Возможно это плохо, но к примеру, d3.select возвращает массив, на который набиндены всякие методы. И for..in с радостью по ним продётся.
                              0
                              Я бы назвал большую часть из приведенных примеров сборником «bad practices» исключая разьве что arguments и indexOf
                                0
                                Ну, часть изложенного здесь — вполне устоявшиеся идиомы языка. Хотя должен отметить, что другая часть — ад и мрак.
                                  –2
                                  indexOf тот ещё bad practice. Приблизительно половина моих знакомых путается, когда ~, а когда !~
                                  Никому не рекомендую использовать, т.к. читать код с этой штуковиной тяжко.
                                    –2
                                    Я из-за этого обычно расширяю прототип Array методом in
                                    Array.prototype.in = function (val) {
                                      return Boolean(this.indexOf(val) + 1);
                                    };
                                    


                                    P. S. Ну, пока в ES6 не примут метод includes (пока что в черновике).
                                      +2
                                      Вы расширяете прототип массива перечислимым свойством, а кто-то следует вредному совету из статьи и обходит массив через for-in. Даже не знаю что хуже.

                                      Ну а ES6 уже пол года как принят и includes туда никак попасть не может. Это часть черновика ES7.
                                        0
                                        обходит массив через for-in
                                        Это за гранью добра и зла.
                                        Это часть черновика ES7.
                                        Да, ES7, его я и имел ввиду.
                                  +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
                                    Тильда здесь не работает потому, что битовые операторы в JavaScript работают лишь с 32-битными значениями.
                                      +3
                                      «распространенный антипаттерн» — но всё же в это надо упереться для начала. А ещё на V8 свет клином не сошелся и оптимизируя под него, можно просесть в остальных браузерах. И главное, сам V8 может измениться, вспомни свои пост про sort, вот и это всё может поменяться. Так что Array.from мой выбор :]
                                        0
                                        Кстати, реально редко использую Array.from, совсем про него позабыл. Спасибо, что напомнил)
                                        А насчет V8. Забывать про других не стоит, но все чаще возникают ситуации, когда js и его реализация в V8 начинают быть не отделимы (по крайней мере сейчас): V8 самостоятельно используется в большом количестве различных проектов, Node.js, Electron, NW.js, и, конечно, Chrome OS, Extensions and Apps — это целый мир =)
                                          0
                                          Так да и это печально на самом деле, но если уже твой путь лежит через V8, то тут уже привет IRHydra ;]
                                            0
                                            Согласись, что если бы существовала одна единственная реализация js — то разработчикам жилось бы пусть менее весло, но значительно проще. Возможно, язык бы не так стремительно развивался, но это уже немного из другой области вопрос =)
                                              0
                                              *менее весело
                                                0
                                                Да, истинная кроссбраузерность — это настоящий вызов :] Но даже если бы был один V8, остаются баги в нем, например один из моих любимых: code.google.com/p/chromium/issues/detail?id=136356
                                              0
                                              Во! Array.from({length: 10})
                                          +1
                                          За всякие "!!bar; ~~anyNum;condition? bar: baz; ~someArray", особенно в составе большого выражения, последующие разработчики могут и покарать.
                                          Вообще странное желание загадить код, лишь бы сократить его размер. Мнимайзер + сжатие и не надо срать себе же в мозг.
                                            +15
                                            Сборник вредных советов.
                                              0
                                              Полезно для разработчиков минимизаторов, типа Google Closure или UglifyJS.
                                              Явное лучше неявного.

                                              !!b
                                              
                                              и
                                              Boolean(b)
                                              


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

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

                                              Код обычно пишется не для конкурса/олимпиады, где нужно показать, что ты знаешь все ньюансы языка. Он пишется для людей.
                                                0
                                                Мне кажется прежде чем называть что то «bad practice»(одни говорят что это плохо читается, другие не понимают) может проблема просто в том что не хотят или не знают должным образом язык. Ведь все относительно, посмотрите на хаскель, кому то покажет он просто адовым, я к тому что если ты знаешь, то уже не кажется что то непонятным.
                                                  +1
                                                  Не должно быть в обычном (не оптимизированном) коде конструкций, для понимания которых нужно держать в голове всю спецификацию языка на сотни страниц. А оптимизированный код должен быть прокомментирован типа

                                                  var intNum = ~~anyNum; // отбрасываем дробную часть и урезаем целую до 32 бит — по ТЗ больше 32 бит быть не может
                                                  +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];
                                                    // ...
                                                  }
                                                  
                                                    0
                                                    Вот из-за таких моментов я вообще предпочитаю не использовать оператор декремента в условии и оператор декремента при указании индекса массива, и честно говоря оператор декремента вообще. Да и неявное приведение типов в условии меня раздражает. И объявление переменных внутри цикла — увы наглая ложь :(.
                                                    То есть я конструкции наподобие приведённой выше пишу примерно следующим образом.

                                                    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
                                                    Уважаемая компания Майл.ру

                                                    Можете мне прокомметировать это письмо по конкурсу «Каникулы в Калифорнии»:
                                                    В первом этапе конкурса «Каникулы в Калифорнии» вы ответили верно не на все вопросы. Но это не повод расстраиваться! Уверены, в следующий раз вам повезет!
                                                    Следите за нашими новостями!


                                                    Что это значит?

                                                    Т.е. я недостаточно точно угадал слово Паскаль в видео ролике, которое написано открытым текстом?
                                                    Или я не смог найти куски картинки, закомментированные в коде главных страничек ваших сайтов?
                                                    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

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

                                                    Простите, но этот конкурс больше похож на лохотрон.
                                                      0
                                                      Привет! Вам ответили в переписке, что были неправильно высланы ссылки на куски картинки (одна ссылка была дублирована, а одна отсутствовала). Проверка происходит в автоматическом режиме, поэтому результаты изменить не представляется возможным, даже если ошибка была допущена по случайности.
                                                        +1
                                                        Опять же камень в огород тех, кто разрабатывал конкурс. Я нашел все коды, просто какой-то из них был случайно введен дважды. Там нельзя было просмотреть уже введенные коды, при том, что картинка все равно «собиралась».

                                                        И вы уверены, что конкурс для настоящих профессионалов должен быть таким?
                                                      0
                                                      Пишите код для людей! Для машин есть аглифаеры! Не делайте из коллеги компилятор и вам будет меньше икаться.
                                                        0
                                                        бррр… Жесть. последний тэг как нельзя лучше подходит к этой теме.

                                                        По делу, кто может подробно объяснить с примерами и сутью:
                                                        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)));
                                                        

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

                                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                        Самое читаемое