Некоторое время назад у меня был маленький гаджет в наборе инструментов, который я всегда нахожу бесценным, когда работаю с событиями мыши . Он оценивает две цели события, чтобы определить, содержит ли одна другую:
function contains(node1, node2)
{
if(node2 == node1) { return true; }
if(node2 == null) { return false; }
else { return contains(node1, node2.parentNode); }
}
Благо и проклятие
Одна из самых красивых вещей в модели событий JavaScript — это всплывающее событие: если событие не перехватывается элементом, который его запускает, событие всплывает до
его родительского элемента. Оттуда это может быть захвачено, или это может всплыть снова, и это продолжается до самого DOM , пока событие не будет захвачено или пока оно не вспыхнет сверху.
Однако, как бы это ни было элегантно и полезно, это делает события мыши более сложными; цель события может не быть элементом, к которому фактически был привязан прослушиватель событий, или события могут появляться в «неправильное» время. Типичным примером этого является перемещение мыши от элемента контейнера к элементу внутри него — событие элемента mouseout сработает в элементе контейнера, даже если мышь фактически не вышла за его пределы .
В большинстве современных браузеров мы можем различать, используя свойство события eventPhase
addEventListener
true
Существуют также специализированные свойства, такие как originalTarget
explicitOriginalTarget
Но все это невозможно в Internet Explorer (по крайней мере, не в тех версиях, которые люди сейчас используют).
Найти что-то, что действительно работает
В Internet Explorer есть встроенный метод оценки цели событий, который называется… (как вы уже догадались)… contains()
Итак, давайте сделаем что-то подобное, которое работает во всех браузерах. И таким образом мы сэкономим некоторую часть кода:
container.onmouseout = function(e)
{
var target = e ? e.relatedTarget : event.toElement;
if(!contains(this, target))
{
//Mouse has left the container element
}
else
{
//Mouse is still inside
}
};
Если бы мы пытались точно воссоздать метод IE, мы бы прототипировали наш метод для Object
HTMLElement
Но мы не должны создавать прототипы пользовательских методов для встроенных объектов , особенно в коде, с которым будут работать другие люди, потому что эти методы будут отображаться в перечислителях, что может вызвать серьезные проблемы для сценариев, которые их не ожидают.
Для интереса, хотя, если бы мы использовали его в качестве прототипа, ему потребовался бы только один аргумент, поскольку сам объект контейнера мог бы ссылаться на this
Object.prototype.contains = function(node)
{
if(node == this) { return true; }
if(node == null) { return false; }
else { return this.contains(node.parentNode); }
}
Во всяком случае, мы не собираемся делать это здесь. Помимо всего прочего, мы хотим, чтобы что-то работало во всех браузерах, включая Internet Explorer (который не поддерживает собственное прототипирование; и даже если бы он это делал, это могло бы перезаписать собственный метод).
Поэтому на практике я склонен использовать его в форме с двумя аргументами как метод любого основного объекта, с которым я работаю; Я нахожу это наиболее удобным, и он не перезаписывает никакие собственные методы:
var myObject = {
...,
contains: function(node1, node2)
{
if(node2 == node1) { return true; }
if(node2 == null) { return false; }
else { return this.contains(node1, node2.parentNode); }
},
...
};
И вот, у вас это есть — короткий кусок кода, который противоречит его полезности (как это часто бывает с коротким кодом). Фактически, в той или иной форме я бы сказал, что этот код превратил примерно в четверть всех написанных мной сценариев! Так ура к этому; и благодарность Джейсону Дэвису , который много лет назад имел оригинальную идею прототипа Объекта — для ударов и хихиканья
— который помог мне отучить себя от Netscape 4.