Javascript.RU

Документ: «парадокс близнецов»

В этом посте я постараюсь рассказать про немаловажный объект 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, сбрасывая все обработчики событий, что же это, следить за каждым «чихом» документа?
Окей, допустим, да. И допустим, что ссылок-то на странице эдак с полтысячи. Сколько памяти съест эта страница?

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

document как корневой элемент DOM и «наследник» событий

Вспомнить, про 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.

UPD: jQuery live

В jQuery, начиная с 1.3.2, есть метод live, которые делает ровно то же самое, что и описано в посте.

Например,

$("a.myClass").live("click", function(){…})

отслеживает на уровне документа щелчок на любом элементе a с
классом myClass.

Пока что им нельзя улавливать фокус элемента, но Рейсиг пишет, что работает над этим. (Но мы-то с вами уже представляем, как это можно сделать, используя capturing и MS-специфичные события, не так ли? ).

В общем, стыд и позор мне, что я не знал про фичу, которая в jQuery уже почти год, но тем не менее, надеюсь, пост оказался Вам полезен. Хотя бы для того, чтобы «окунуться» глубже в события яваскрипта.

+2

Автор: e1f, дата: 27 января, 2010 - 18:02
#permalink

Честно говоря, пользоватся я считаю возможным только с недавних пор, поскольку только в 1.4 появилась возвожность вешать лив не на документ, а на контекст.


Автор: Гость (не зарегистрирован), дата: 16 апреля, 2022 - 01:47
#permalink

Автор: Гость (не зарегистрирован), дата: 16 апреля, 2022 - 02:33
#permalink

Автор: Гость (не зарегистрирован), дата: 16 апреля, 2022 - 12:15
#permalink

Автор: Гость (не зарегистрирован), дата: 16 апреля, 2022 - 12:38
#permalink

Отправить комментарий

Приветствуются комментарии:
  • Полезные.
  • Дополняющие прочитанное.
  • Вопросы по прочитанному. Именно по прочитанному, чтобы ответ на него помог другим разобраться в предмете статьи. Другие вопросы могут быть удалены.
    Для остальных вопросов и обсуждений есть форум.
P.S. Лучшее "спасибо" - не комментарий, как все здорово, а рекомендация или ссылка на статью.
Содержание этого поля является приватным и не предназначено к показу.
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Разрешены HTML-таги: <strike> <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <u> <i> <b> <pre> <img> <abbr> <blockquote> <h1> <h2> <h3> <h4> <h5> <p> <div> <span> <sub> <sup>
  • Строки и параграфы переносятся автоматически.
  • Текстовые смайлы будут заменены на графические.

Подробнее о форматировании

CAPTCHA
Антиспам
6 + 10 =
Введите результат. Например, для 1+3, введите 4.
 
Поиск по сайту
Другие записи этого автора
subzey
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Популярные таги
Последние комментарии
Последние темы на форуме
Forum