Jun 16

На работе столкнулись с “особенностью” работы jQuery.live(), на которую хотелось бы обратить внимание, потому как, судя по всему, отнюдь не все о ней знают (и в результате чего пишут неработающий код).

Итак, простой пример - навешивание двух событий на один и тот же элемент:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
  <title>jQuery.live() test page</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript">
  jQuery(function() {
    $("a").bind("click", function(e) {
      alert("a");
      return false;
    });
    $("a").bind("click", function(e) {
      alert("b");
      return false;
    });
  });
  </script>
</head>
<body>
  <a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>

Пример можно посмотреть здесь. В результате клика на ссылку - получаем 2 alert-а, всё хорошо, ожидаемо и предсказуемо.

Переписываем код для работы с jQuery.live(). Для тех, кто в танке - live() вешает событие не на сам элемент, а на document. В результате bubbling-а событие, которое произошло над каким-либо элементом поднимается вверх по DOM-дереву и соответственно вызывает обработчики всех элементов, которые оно встретит. Если вы и этого не знали - то вам не нужно читать мой блог, а пора идти и покупать книгу по JavaScript-у (мне, кстати, тоже давно пора, но всё никак не соберусь). Итак, в конце концов событие доходит до document-а и обработчики вызываются у него. Обработчик, который устанавливает jQuery.live() проверяет - соответствует ли event.target (а именно здесь хранится обьект DOM-дерева, с которым произошло событие) соответствующему селектору (в данном случае - это селектор “a”) и если соответствует - то выполняет обработчик.

Преимущества и недостатки - это тема отдельной статьи. Если не уклонятся в сторону оптимизации, то основным преимуществом, на мой взгляд, является тот факт, что обработчики, навешенные live()-ом будут запускаться даже для элементов, которые были динамически добавлены на страницу, в отличии от bind()-обработчиков, которые на эти элементы нужно будет навешивать вручную (если непонятно, почему это работает именно так - читаем предыдущий абзац, если все равно непонятно - идем покупать всё ту же книгу).

Далее - зачем нужен “return false” в конце обработчика? Он предотвращает от того, чтобы вызывалось действие по умолчанию (в данном случае - переход по ссылке и событие не поднималось выше). Чаще всего JS-разработчики вообще не думают о bubbling-е и под “return false” понимают только “отмену действия по умолчанию”, ну или они вообще не знают, что именно происходит и пишут “return false”, потому что так работает.

Такое отношение jQuery частенько прощает. Но не в случае с live()-методом. Попробуем запустить следующий пример:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
  <title>jQuery.live() test page</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript">
  jQuery(function() {
    $("a").live("click", function(e) {
      alert("a");
      return false;
    });
    $("a").live("click", function(e) {
      alert("b");
      return false;
    });
  });
  </script>
</head>
<body>
  <a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>

В результате клика теперь выскакивает только один alert. Пора обратится к документации:

# To stop further handlers from executing after one bound using .live(), the handler must return false. Calling .stopPropagation() will not accomplish this.

Хаха. В данном случае jQuery интерпретирует false “несколько иначе”. :)

Для того, чтобы “пофиксить” подобный баг нужно обратится все к тому же bubbling-у и обработке событий и сделать именно то, что предполагается разработчиком - “отменить действие по умолчанию”. Это делается с помощью метода event.preventDefault() (пример):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
  <title>jQuery.live() test page</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  <script type="text/javascript">
  jQuery(function() {
    $("a").live("click", function(e) {
      alert("a");
      e.preventDefault();
    });
    $("a").live("click", function(e) {
      alert("b");
      e.preventDefault();
    });
  });
  </script>
</head>
<body>
  <a href="http://blog.fxposter.org/">Блог FX-а</a>
</body>
</html>

И самое главное (барабанная дробь!) - при использовании bind() для навешивания обработчиков preventDefault() тоже можно использовать!

Наткнулись мы на эту “фичу”, когда у нас почему-то перестали вызываться некоторые обработчики

Напоследок, замечу еще одно - элемент, на который навешено хотя бы один обработчик события через bind() с “правильно работающим return false”, никогда не будет вызывать никакие live()-события. ;)

Так что будьте бдительны и не забывайте об особенностях обработки событий в JS. Удачи.

written by fxposter \\ tags: , , ,

One Ping to “Особенности работы jQuery.live()”

  1. Осторожно! jQuery 1.9.0 не поддерживает live() « Блог о CMS Says:

    […] нашёл дельный материал, который не могу не попиарить Особенности работы jQuery.live(). Материал довольно старый, но я так и оказался не в […]


16 Responses to “Особенности работы jQuery.live()”

  1. 1. point Says:

    Работает так:

    if ( ret === false ) {
    event.preventDefault();
    event.stopPropagation();
    }

    То есть “return false;” не более чем удобство.

    BTW,
    live(“submit”,….) не работает в IE . Да и вообще, с live() нужно быть очень внимательным :)

  2. 2. ciklop Says:

    return false; тянется со времён onclick=”…..;return false;”
    При навешивании событий по “нормальному” как раз и надо использовать event.preventDefault();
    Так что это скорее не фича, а просто неправильное использование возврата из функции. Как всегда – документация важна.
    Сам тоже раньше писал return false; но потом более внимательно прочитал про событийную модель JS и понял что return false; это порочный путь. И некорректной работы live() просто избежал.

  3. 3. Yarixxx Says:

    Спасибо за напоминание. Если бы я знал про существование live() месяц назад – сэкономил бы кучу времени, а не городил огород с повторным навешиванием обработчиков на свежесозданные элементы.

  4. 4. Dmitry Says:

    Всегда останавливаю события через preventDefault(). Так как live и bind могут навешавать несколько обработчиков на один элемент – то использую также stopPropagation() если надо остановить выполнение именно на этом обработчике. В остальных стоит проверять что событие остановленно через event.isDefaultPrevented() и event.isPropagationStopped()

  5. 5. FX Poster Says:

    point
    С live не только submit не работает. ;)

  6. 6. Mr.K Says:

    Забавно, сам с этой штукой столкнулся неделю назад! Примерно тогда же узнал, что помимо live() теперь есть еще и delegate()

  7. 7. Смирнофф Says:

    тут есть еще один прикол, для того чтобы не подниматься до самого document используй delegate или книжку купи :)

  8. 8. fxposter Says:

    А без разницы. delegate или live – принципы работы те же.

  9. 9. CTAPbIu_MABP Says:

    да принцип тот же, только при большом дом дереве совсем не обязательно “всплывать” аж до документа
    тем более когда повешено много лайвов на документ надо сделать много проверок над e.target подходит ли он хоть под один селектор, а так надо сделать проверку например только на непосредственном родителе

  10. 10. fxposter Says:

    Ну это понятно. Просто пост не совсем об оптимизации этого момента :)

  11. 11. CTAPbIu_MABP Says:

    меня просто предложение про танк зацепило

  12. 12. Maxim Says:

    2 Смирнофф, CTAPbIu_MABP:
    delegate – это синоним live, только у него другой формат вызова.
    В обоих функциях есть параметр, который указывает контекст (DOM элемент), в котором обрабатывается указанное событие. Т.е., если указать этот контекст, то jQuery не будет проверять условие до самого document, а остановится на указанном контексте.

  13. 13. CTAPbIu_MABP Says:

    2 Maxim
    ага очень удобно передавать в контекст live DOMElement вместо селектора. удачи :)

  14. 14. Maxim Says:

    Пардон, я слегка ошибся в терминах, просто я хотел передать этот код из jquery.js (1.4)

    delegate: function( selector, types, data, fn ) {
    return this.live( types, data, fn, selector );
    }

    CTAPbIu_MABP правильно меня исправил, нужно передавать селектор, а не ДОМ элемент (прошу прощения). Но основная мысль заключалась в том, что можно избежать поиска элемента до самого document.
    Сами разработчики на странице док-ции предлагают такой вариант (еще не обновили документацию для нового параметра):

    $(‘div.clickme’, $(‘#container’)[0]).live(‘click’, function() {
    // Live handler called.
    })

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

  15. 15. CTAPbIu_MABP Says:

    >> не всегда нужно переживать о скорости проверки элементов, если количество проверяемых элементов не исчисляется сотнями или тысячами

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

    если ты хочешь подробнее узнать мои мысли об оптимизации читай мой блог

  16. 16. Jura Says:

    Спасибо, интересная и полезная статья.

Leave a Reply