Почему стоит использовать специальные классы для привязки событий

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

    На мой взгляд существует 3 популярных способа навесить событие на элемент в думе:
    1. Через ID
    2. Через то, что досталось от верстки
    3. Через специальные классы



    1. Привязка через ID


    Что может быть проще? Уверен многие, при первых знакомствах с JS так делали, а кто-то делает и сейчас. У метода, естественно, есть плюсы. Во-первых, это высокая скорость, обусловленная тем, что элементы с id находятся в глобальном объекте window и их даже искать не надо. Во-вторых, элементы легко найти при отладке, как из шаблона, так и через навешивание событий.
    К примеру, у нас есть код

    <div class="main">
    <p>
    Новейшая система слива обеспечивает качественную и быструю работу спускных клапанов. Происшествия возникают лишь в 3- случаях из 10.
    <a href="#" id="system-detail">Подробнее</a>
    </p>
    </div>
    


    Согласитесь, легко найти по #system-detail в каком месте к этому элементу привязывается событие. Также это справедливо и тогда, когда у нас есть js код, а надо найти шаблон.

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

    2. Привязка через то, что досталось от верстки


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

    <div class="block_content">
    <p class="left b_font red_font"><a href="#">Пропустить рекламу вперед</a></p>
    <div class="left inline-block">
    <p>
    Современно соотечественники не в состоянии созидать без современных вещей! Своевременно смените старые вещи на совершенные. Своенравные товарищи, будут осовременены принудительно.
    <a href="#">Осовремениться</a>
    <p class="hidden">
    <span>Ну а если вам лень куда-то ехать можете<a href="#">Вызвать подмогу на дом</a></span>
    </p>
    </p>
    </div>
    </div>
    


    Скорей всего разработчик написал что-то в духе

    $('.block_content .inline-block a:first'); // Осовремениться
    $('.block_content .left.b_font.red_font'); // Пропустить рекламу вперед
    $('.block_content .inline-block a:last'); // Вызвать подмогу на дом
    


    Помимо большого количества селекторов в запросе, эта команда очень хрупкая. Стоит нам поменять класс одного из элементов или их расположение, как все рассыпется. Кроме того такие запросы очень сложно отлаживать.
    Как узнать где навешивается событие на «Осовремениться»! Как будем искать? По частям только и то наугад. Будем искать по одному селектору, потом по другому и так пока не найдем что-то похожее, после чего еще будем проверять то ли это, или нет. В случае, если нам надо продублировать действие на другой элемент, нам нужно повторно писать присвоение(или добавлять в старый селектор через запятую).

    Чтобы модифицировать такой запрос, нужно посмотреть кучу шаблонов, чтобы быть уверенным в том, что он не привяжется к чему-нибудь похожему, в случае если это один файл на весь проект.
    А вот мы передаем шаблон верстальщику для правки цвета с красного на синий, и что происходит? Ссылка больше не работает.
    Хорошо может разбираться в таком шаблоне только тот, кто его сам и написал, и то, довольно непродолжительное время.

    3. Привязка через специальные классы


    И вот мы подошли к последнему пункту. Для привязывания событий добавляется специальный класс, к примеру, js-new-message или app-open-window, который не используется для задания стилей, а только нужен для того, чтобы навесить событие. Возьмем предыдущий пример и доработаем его

    <div class="block_content">
    <p class="left b_font red_font"><a href="#" class="app-skip-advert">Пропустить рекламу вперед</a></p>
    <div class="left inline-block">
    <p>
    Современно соотечественники не в состоянии созидать без современных вещей! Своевременно смените старые вещи на совершенные. Своенравные товарищи, будут осовременены принудительно.
    <a href="#" class="app-modern">Осовремениться</a>
    <p class="hidden">
    <span>Ну а если вам лень куда-то ехать можете<a href="#" class="app-help-me">Вызвать подмогу на дом</a></span>
    </p>
    </p>
    </div>
    </div>
    


    $('.app-skip-advert'); // Пропустить рекламу вперед
    $('.app-go-to-modern'); // Осовремениться
    $('.app-help-me'); // Вызвать подмогу на дом
    


    Разница очевидна. Код не зависит от стилей, если нужно продублировать действие, достаточно присвоить нужный класс в нужное место. Поиск, как элементов, так и кода, легок в обоих направлениях. Поддержка такого кода занимает минимум времени. При использовании нескольких шаблонов на одной странице, в случае если привязка идет не к глобальному объекту, а к корню шаблона, код будет работать хорошо. Да, могут быть конфликты, но их отследить проще, чем во втором варианте.

    Only registered users can participate in poll. Log in, please.

    Какой из способов используете вы?

    • 23.2%Привязка по ID103
    • 15.5%Привязка по тому, что есть в верстке69
    • 45.7%Привязка по специальным классам203
    • 15.5%Другое69
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 33

      +3
      Вообще, есть более удобный и наглядный префикс «js-*»
        0
        Угу, я его привел в пример сразу под заголовком третьего способа.
        +4
        Зачем заморачиваться с классами, когда есть data-атрибуты? Ваши примеры выглядели бы у меня вот так:

        $('*[data-app="skip-advert"]'); $('*[data-app="go-to-modern"]'); $('*[data-app="help-me"]');

        Почти полностью исключается случайная стилизация.
          +9
          Эта конструкция будет работать медленнее. Поскольку нет команд выборки по атрибутам.
            0
            Что вы имеете в виду?

            document.querySelectorAll() - оно?
              +4
              Да, это универсальный селектор.
              Сравните скорости.
              jsperf.com/class-vs-attr-2
                0
                Согласен, медленнее.

                Но тут вопрос не только в скорости, а и в удобстве, не так ли? Иначе надо везде юзать id, который ещё быстрее. Вот у меня ваш тест показал 36 тыщ операций в секунду при использовании jQuery и data, а сколько реально может быть нод на странице, которые будут обрабатываться скриптом? Ну пускай тыща. Если у вас не специфический проект, где нужна мега-скорость, то разницей в N миллисекунд ради удобства можно и пренебречь.

                Или мы тут об абстрактных «попугаях» разговариваем?
                  0
                  Да, я с вами согласен. Я за любой способ, кроме первых двух. Атрибуты имеют столько же прав на существование, как и классы.
                  Разве что, несколько уменьшается читаемость и чистота, из-за дополнительных скобок и кавычек.
                    +2
                    Я прошёл все способы, и ещё не упомянутый здесь атрибут role с удобным плагином для коротких селекторов по ролям.
                    Последние несколько проектов использовал .js-*, и поверьте это не менее удобнее чем любой другой способ. А главное back-end разработчики не теряются, так как в HAML-разметке это будет выглядеть так же лаконично, как в селекторах, без лишних знаков.
                    Более того, эта нотация рекомендована в гайдах гитхаба.
                      0
                      Кстати, может произойти обратное описанному в статье: верстальщик понавешает свои стили на классы .js-*. Поэтому вариант с role видится очень заманчивым
                    0
                    Так сравнение некорректное. Медленее, да, но не на столько, как показано в тесте.

                    Сравните.

                    getElementsByClassName возвращает HTMLCollection, а querySelectroAll возвращает NodeList. Это совершенно разные вещи и используются по-разному.
                0
                Плюс такой конструкции в том, что явно отделяется оформление от кода.
                  0
                  Data ничем не лучше классов. На data- в одном элементе может быть куча всего навешано, и ваш атрибут затеряется. А еще если кто-то заведет ваш data-app для своих целей?
                    0
                    Кто-то для своих целей может и должен использовать role, т.к. это атрибут, относящийся к семантике документа. В отличие от него атрибут data-app не несёт никакой семантической нагрузки и поэтому он лучше.
                      0
                      Такие ситуации крайне редки. К тому же мы можем писать так: role=''js-action''. Тогда мы точно никому мешать не будем. Плюс спецификация не запрещает использование множества role, так что если кому надо добавит свой role. А вот к вашему data-app свой data-app я добавить не могу. Конечно, это сферично в вакууме, но тем не менее.

                      А еще если я вызову var foo = bar.dataset, то в foo окажется data-app. При желании можно придумать пример, где это сломает логику работы приложения.
                      0
                      Надо обходиться без «если». Если не хотите превратить проект в басню «Лебедь, рак и щука», нужно вводить какие-то правила. Как пример: атрибут data-app используется для определения роли элемента (привязки событий, обращения к нему из js итд).
                    0
                    Хотелось бы отметить, что, когда мы просто пишем $('.app-skip-advert'), подразумевая, что выбрали при этом один-единственный элемент на странице, это мало чем отличается от использование id — в любом случае, при наличии двух элементов будут проблемы.

                    Мощь привязки по специальным классам проявляется при поиске соответствующих элементов в некотором контейнере. Так, $('#mainblock.app-skip-advert') защитит от появления такого же класса в соседнем блоке, а $('.app-skip-advert', block) позволит оперировать ссылкой внутри некоторого повторно используемого компонента страницы.
                      0
                      Верно. Может я непонятно написал, но в конце статьи есть:
                      При использовании нескольких шаблонов на одной странице, в случае если привязка идет не к глобальному объекту, а к корню шаблона, код будет работать хорошо.

                      Это я и имел ввиду.
                      Кроме того, вы, видимо, подразумеваете, что использовать один и тот же класс, для разных задач.
                        0
                        Скажем так — допускать использование одного и того же класса разными программистами для разных задач на одной странице.
                      –5
                      А также для привязки событий к элементам можно создать маленький (ну или как получится) js-объект типа:

                      AdminList = {
                      
                        showFilter: function() {}
                      
                      };


                      и к html-элементам просто применять навешивание:

                      <span onclick="AdminList.showFilter();">Фильтры</span>


                      В таком случае вообще не нужно искать, что/где/когда навешивалось на элемент — это сразу видно. Другой плюс — после перезагрузки такого html-блока аяксом, например, обработчик всегда сработает, чего не скажешь о часто встречающемся $('.class').click() на элементе (ну хоть live/on есть, что радует)…
                        +3
                        Это, конечно, красиво и очевидно, когда нам нужно привязать одно событие к одному элементу. А если нужно привязать событие ко всем дочерним элементам (например, запрос результатов поиска по изменению значений в форме), если привязывать несколько событий к одному элементу, итд, то подобный способ ИМХО превращается в боль. После переименования функции при рефакторинге, опять же, переименовывать ее во всех шаблонах.
                          0
                          Переименовать функцию с современными-то IDE типа PhpStorm — гораздо легче, чем оперировать классами, на которые что-то навешено…
                          Если нужно привязать несколько событий к одному элементу — это изврат какой-то, но в любом случае сделайте обертку на этими несколькими действиями, из которой по очереди вызовите нужные…
                            +1
                            Переименовать функцию с современными-то IDE типа PhpStorm — гораздо легче, чем оперировать классами, на которые что-то навешено…

                            Чем легче-то? В чем принципиальная сложность оперирования правильно составленными классами/ролями?

                            Если нужно привязать несколько событий к одному элементу — это изврат какой-то, но в любом случае сделайте обертку на этими несколькими действиями, из которой по очереди вызовите нужные…

                            Почему изврат-то? Нам нужно, например, обрабатывать клик по кнопке и обрабатывать наведение мыши, предположим. И превращается наш элемент из
                            <button class=".kill-all-humans-btn" data-tooltip="Dis button killz all humanz">Don't touch!</button>

                            в ужасное, на мой взгляд,
                            <button onclick="UsefulFunctions.killAllHumans" onmouseover="Tooltips.show('Dis button killz all humanz')" onmouseout="Tooltips.hide">Don't touch!</button>


                            В добавок, на класс можно навешивать не только события, но и поведение вообще. Т.е. к нему можно обращаться из js-кода.
                          0
                          Тоже использую такой метод, во всех местах, которые генерируются или потенциально могут меняться динамически.
                          Оказалось проще создать исчерпывающий набор функций, завернутых в объект, чем дергать из десятка разных мест различные инициализаторы биндингов.
                          С делегированием событий тоже как-то не срослось: навешивать различные обработчики 500 разных кнопок на один контейнер — это как-то совсем негуманно.
                          Кстати, ВКонтакте тоже использует onclick="..." в ленте.
                          +1
                          Отличная, на мой взгляд, альтернатива специальным классам — роли и role.js.
                            +2
                            как уже писали выше — пробовалось и в итоге для понимания бэкэндщиков было трудным, в тестах сложно искать по ролям, да и по скорости проигрывает классам вроде как
                              0
                              Бэкендщикам бэкендово, фронтендщикам фронтендово. Как вообще роли касаются бэкенда? Да и чем они сложнее для понимания, чем классы? При использовании Рельсы в Slim они добавляются гемом role-rails, в Haml — role-haml.
                              При тестах, опять таки, особых проблем замечено не было, особенно при использовании паттерна Page Object.
                              Ну а экономией миллисекунд скорости на фронте я лично не страдаю.
                              0
                              а еще лучше совместить 2 подхода и писать role=''js-action''
                              0
                              Уже не первый год для стилей использую классы вида «class-name», а для javascript — вида «jsClassName». При первом же взгляде на разметку можно понять, какие классы для чего созданы.
                                0
                                В проекте на работе нашел следующий код:

                                <a href="#" class="command-delete trash">Удалить в корзину</a>
                                <a href="#" class="command-delete permanent">Удалить навсегда</a>
                                <script>
                                $(function() {
                                    $('.remove').bind('click', function() {
                                        var action = $(this).prop('class').replace('command-delete ', '');
                                        SomeBackend.RemoveBook(id, action);
                                    });
                                });
                                </script>
                                

                                После этого — только data-*.
                                  0
                                  class=«js-someAction» — для привязки событий, data-someProperty=«value» — для параметров, передаваемых в эти события. Так все логически разделено, а значит удобно, и свойства используются по прямому назначению.
                                  0
                                  У меня логика такая… если событие подразумевает «одноразовость» — т.е. для одного элемента и никак иначе, то привязываю по ID. Если одно и то же событие может быть вызвано с разных элементов (пусть даже с разными параметрами), то классы.
                                    0
                                    left, b_font, red_font — это ужасные имена CSS-классов. Не надо так.

                                    Only users with full accounts can post comments. Log in, please.