Документ: «парадокс близнецов»
В этом посте я постараюсь рассказать про немаловажный объект document и манипуляции с элементами внутри него.
Предположим, у нас есть прикладная задача — нам нужно сделать выпадающее меню. Большинство из вас уже, наверняка, решали эту задачу не один раз и разными способами. Наиболее частое решение, которое я видел, выглядит примерно так:
// jQuery
$(document).ready(function(){
$("#menu li").mouseover(function(){
$("ul", $this).css("display", "block");
});
$("#menu li").mouseout(function(){
$("ul", $this).css("display", "none");
});
})
Код не рабочий, но так или иначе, наверное, понятно, что имеется в виду: при наведении на элемент с нужным классом его потомок показывается. При уведении убирается. Чтобы к моменту запуска кода элементы уже существовали в документе, запускаем его после полной загрузки.
Какие же есть минусы у этого подхода?
Перво-наперво, приходится ждать события load . Это без проблем, если у Вас обычная страница и быстрый интернет. А, скажем, счетчик LiveInternet, вставленный в страницу будет грузиться 30 секунд? Ну, тогда эти 30 секунд пользователь не сможет воспользоваться меню.
Можно использовать DOMContentLoaded и onreadystatechange , и некоторые даже так и делают.
Можно вставлять код запуска в конец страницы, но, вдруг, пользователь не захочет загружать страницу до конца? В самом деле, у него GPRS-соединение, и нужно всего-лишь перейти в раздел «контакты». А хрен-с! Загружайте всю!
А если у нас добавился элемент уже после загрузки страницы? (Крайне нехарактерное поведение для навигационного меню, но тем не менее ) Ему же тоже надо добавлять обработчик, а иначе возникнет эдакий «парадокс близнецов» — одинаковые элементы ведут себя совсем по-разному.
Решение для jQuery есть — плагин liveQuery, отслеживающий мутации DOM-дерева.
Но дадим ему другую задачу: при щелчке на внешнюю ссылку переход должен осуществляться в новом окне.
Вот тут-то и можно оценить полный масштаб «катастрофы», в отличие от пунктов меню текстовые ссылки еще как могут загружаться аяксом, содержимое может меняться через innerHTML, сбрасывая все обработчики событий, что же это, следить за каждым «чихом» документа?
Окей, допустим, да. И допустим, что ссылок-то на странице эдак с полтысячи. Сколько памяти съест эта страница?
Все эти ужасы на практике встречаются редко, но тем не менее, что же делать в таком случае?
Вспомнить, про event bubbling. Если событию, например, click ничего не помешает, оно «передается» его предку. А потом предку его предка. Доходит до <body> , <html> , а потом document . И выходит, что любое непогашенное событие click мы можем обработать на уровне document .
Вот живой пример, запустите его:
/* структура не замороченная, чтобы можно было проще разобрать */
var myOnclick = function(e){
var thisFunction = arguments.callee;
if (thisFunction.runTimes-- <= 0){
myOnclickUnattach(thisFunction);
return;
};
var trigger = e.srcElement||e.target;
alert("Произошел щелчок по " + (trigger.tagName||((trigger == window)?"пустому месту в окне":false)||"неизвестному природе элементу") +
"\r\nЕще будет отслежено щелчков: " + thisFunction.runTimes);
};
var myOnclickAttach = function(functionToAttach){
functionToAttach.runTimes = 3;
if (document.addEventListener) document.addEventListener('click', functionToAttach, false)
else if (document.attachEvent) document.attachEvent('onclick', functionToAttach);
};
var myOnclickUnttach = function(functionToUnattach){
functionToAttach.runTimes = 0;
if (document.removeEventListener) document.removeEventListener('click', functionToUnattach, false)
else if (document.detachEvent) document.detachEvent('onclick', functionToUnattach);
};
myOnclickAttach(myOnclick);
А теперь к практике! Цель — открывать внешние ссылки в новом окне браузера.
if (!window.externalLinker){ /* не надо переопределять объект */
window.externalLinker = { /* все связанные функции делаем методами одного объекта */
handler: function(e){
var trigger = e.srcElement || e.target; /* кто вызвал событие? */
var testElement = trigger;
while (testElement){ /* проверяем себя и всех своих родителей на предмет того, ссылка («анкер», чтобы не путаться) ли они */
if (testElement.tagName && testElement.tagName.toLowerCase() == "a" && testElement.href) break; /* если да, testElement будет содержать ссылку на анкер */
testElement = testElement.parentNode;
};
if (!testElement) return; /* а если нет, ничего не делаем */
if (/https?/.test(testElement.protocol) && testElement.hostname != location.hostname && !testElement.target){ /* т.е., если протокол http или https, домены не совпадают и target уже не задан явным образом */
testElement.target = "_blank"; /* задаем target. Переход по ссылке произойдет после того, как эта функция отработает, так что время у нас есть */
setTimeout(function(){delete testElement.target}, 0); /* после того, как отработает вся цепочка, включая default action, возвращаем все на свои места */
};
},
enable: function(){ /* включить */
if (document.addEventListener) document.addEventListener('click', this.handler, false)
else if (document.attachEvent) document.attachEvent('onclick', this.handler);
},
disable: function(){ /* выключить */
if (document.removeEventListener) document.removeEventListener('click', this.handler, false)
else if (document.attachEvent) document.detachEvent('onclick', this.handler);
}
};
};
externalLinker.enable();
А вот и ссылочки, чтобы проверить поведение:
Ну, а если Вам надоело такое поведение, Вы можете очень легко его отключить:
if (window.externalLinker){
window.externalLinker.disable();
};
Этот способ не панацея, а лишь еще один метод в коллекцию разработчика, — мы не захламляем документ одинаковыми методами и не гоняемся за элементами, но при этом жертвуем процессорным временем при каждом вызове события. Об этом следует помнить, и не стóит бездумно ставить подобную обработку на тот же mousemove .
В jQuery, начиная с 1.3.2, есть метод live , которые делает ровно то же самое, что и описано в посте.
Например,
$("a.myClass").live("click", function(){…})
отслеживает на уровне документа щелчок на любом элементе a с
классом myClass .
Пока что им нельзя улавливать фокус элемента, но Рейсиг пишет, что работает над этим. (Но мы-то с вами уже представляем, как это можно сделать, используя capturing и MS-специфичные события, не так ли? ).
В общем, стыд и позор мне, что я не знал про фичу, которая в jQuery уже почти год, но тем не менее, надеюсь, пост оказался Вам полезен. Хотя бы для того, чтобы «окунуться» глубже в события яваскрипта.
|
Честно говоря, пользоватся я считаю возможным только с недавних пор, поскольку только в 1.4 появилась возвожность вешать лив не на документ, а на контекст.
Roksa family
Roksa mem
Strony takie jak roksa
Krynica morska roksa
Отправить комментарий
Приветствуются комментарии:Для остальных вопросов и обсуждений есть форум.