система событий браузера

JavaScript браузер

введение

JavaScriptа такжеHTMLВзаимодействие между ними осуществляется посредством событий. События — это определенные моменты взаимодействия, происходящие в документе или окне браузера. Система событий браузера относительно сложна. Хотя все основные браузеры реализовали "DOM2события класса", но сама эта спецификация не охватывает все типы событий, сDOM3появляется класс,DOMмероприятиеAPIстать богаче. Кроме того, объектная модель браузера (BOM) также поддерживает некоторые события, но эти события не связаны с объектной моделью документа (DOM) непонятно, потому чтоBOMСобытия давно не имеют норм для подражания(HTML5Инструкции были даны позже). Эта статья в основном знакомит с браузеромDOMСистема событий включает в себя три этапа потока событий и три разных способа обработчиков событий (DOM0,DOM2,IE),учитываяIEКак быть с совместимостью разницы между обработкой события и объектом события в , как применить свойства объекта события к практическим приложениям и различиям между ними, а также последовательности захвата события и всплытия.

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

  • DOMСуществует три способа обработчика событий,DOM0изonType,IE9нижеattachEventа такжеdetachEvent,DOM2изaddEventListenerа такжеremoveEventListener.
  • DOM2Преимущество класса в том, что его можно пройти черезaddEventListenerТретий параметр, указывающий, следует ли захватывать или всплывать, и может быть одним и тем же.DOMэлементы регистрируют несколько обработчиков событий одного и того же типа;DOM0Поддержка только одного обработчика событий для каждого события
  • DOM0а такжеDOM2обработчики событий автоматически передаются вeventобъект; в IEeventОбъект зависит от метода указанного обработчика событий, поэтому в IE будетwindow.event,eventдва случая;eventОбъекты имеют некоторые полезные свойства, такие как цель,currentTarget,preventDefault,stopPropagation,stopImmediatePropagationЖдать
  • дляDOM0изontype, методы привязки к поведению события элемента выполняются в фазе всплытия (или целевой фазе) поведения события текущего элемента. дляDOM2изaddEventListener, для максимальной совместимости обработчики событий в большинстве случаев добавляются в фазу всплытия событий. Особо не требуется, и регистрация обработчиков событий на этапе захвата событий не рекомендуется.
  • Следует учитывать совместимость обработки обработчиков событий.DOM0а такжеIE9Следует учитывать следующие методы обработки событий, обработку совместимости объекта события и свойства объекта события.IEразница в
  • event.stopPropagation()метод, чтобы предотвратить всплытие события к родительскому элементу, предотвращая выполнение любых обработчиков родительских событий (обычно мы думаемstopPropagationОна используется для предотвращения всплытия событий, по сути, эта функция также может предотвращать захват событий)
  • event.targetуказывает на элемент, вызвавший инициирующее событие, иevent.currentTargetЭто элемент, привязанный к событию, только целевой элемент, на который нажали.event.targetбудет равноevent.currentTarget

Большинство браузеров, поддерживающих потоковую передачу событий DOM, реализуют определенное поведение; хотя в спецификации «события уровня DOM2» явно указано, что этап захвата не включает цели событий, IE9, Safari, Chrome, Firefox и Opera 9.5 и более поздние версии события на объекте события во время фазы захвата. В результате есть две возможности манипулировать событиями на целевом объекте.

поток событий

Поток событий описывает порядок получения событий со страницы. Но интересно,IEа такжеNetscapeКоманда разработчиков на самом деле придумала две диаметрально противоположные концепции потока событий. Поток событий IE — это всплывающий поток событий, а стандартный поток событий браузера — это поток захвата событий. но,W3CДля разработки стандарта был выбран компромисс: сначала захват, а затем барботирование (черезaddEventListeneТретий аргумент, заданный r, поддерживает как всплытие, так и захват). В частности, тот жеDOMЭлементы могут регистрироваться для нескольких событий одного и того же типа черезaddEventListenerзарегистрироваться на мероприятия,removeEventListenerотменить мероприятие.

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

DOMПоток событий делится на три этапа:捕获阶段,目标阶段,冒泡阶段. Сначала вызывается функция-обработчик фазы захвата, затем вызывается функция-обработчик целевой фазы и, наконец, вызывается функция-обработчик фазы всплытия. (На картинке ниже нет тега html)

img

(1) Фаза захвата: события изwindowСтадия, на которой объекты распространяются сверху вниз к целевому узлу;

(2) Целевой этап: этап, на котором реальный целевой узел обрабатывает событие;

(3) Фаза пузыря: событие идет снизу вверх целевого узла.windowФаза распространения объекта.

Захват идет сверху вниз, события сначалаwindowобъект, а затемdocument(объект), затемhtmlярлык (черезdocument.documentElementПолучатьhtmlметка), затемbodyярлык (черезdocument.bodyПолучатьbodyярлык), затем следуйте обычнымhtmlСтруктура передается слой за слоем и, наконец, достигает целевого элемента.

Процесс всплытия событий — это просто процесс, обратный захвату событий. Далее, давайте рассмотрим пример всплытия событий:

// 例3
<div id="outer">
    <div id="inner"></div>
</div>
......
window.onclick = function() {
    console.log('window');
};
document.onclick = function() {
    console.log('document');
};
document.documentElement.onclick = function() {
    console.log('html');
};
document.body.onclick = function() {
    console.log('body');
}
outer.onclick = function(ev) {
    console.log('outer');
};
inner.onclick = function(ev) {
    console.log('inner');
};

img

как мы упоминали нижеonclickМетоды привязки к поведению события элемента выполняются в фазе всплытия (или целевой фазе) поведения события текущего элемента.

Уровень события DOM

DOMВсего существует четыре уровня:DOM0级,DOM1级,DOM2级а такжеDOM3级. а такжеDOMСобытия делятся на 3 уровня:DOM 0обработка событий уровня,DOM 2обработка событий уровня иDOM 3обработка событий уровня. из-заDOM 1В классе нет контента, связанного с событиями, поэтому нетDOM 1событие уровня. и потому, чтоIEи другие браузеры вDOM2Обработка событий различается на уровне, поэтому методы обработки событий можно разделить на три категории, а именно:DOM0,DOM2,IE. Ниже изDOMуровень повышен

События уровня DOM 0

el.onclick=function(){}

var btn = document.getElementById('btn');
 btn.onclick = function(){
     alert(this.innerHTML);
 }

Если вы хотите привязать несколько событий одного типа к одному и тому же элементу/метке (например, привязать 3 события щелчка к элементу btn выше), это не разрешено.. привязка события DOM0, метод привязки к поведению элемента,Эти методы выполняются в фазе всплытия (или целевой фазе) текущего поведения события элемента..

События DOM уровня 2

el.addEventListener(event-name, callback, useCapture)

  • event-name: имя события, может быть стандартным событием DOM
  • callback: функция обратного вызова, когда событие срабатывает, функция будет введена с параметром текущего объекта события event
  • useCapture: значение по умолчанию — false, что означает, что дескриптор события выполняется на этапе всплытия (или событие всплытия зарегистрировано), а значение true означает, что дескриптор события выполняется на этапе захвата (или зарегистрированное событие является событием захвата). )
var btn = document.getElementById('btn');
btn.addEventListener("click", test, false);
function test(e){
	e = e || window.event;
    alert((e.target || e.srcElement).innerHTML);
    btn.removeEventListener("click", test)
}
//IE9-:attachEvent()与detachEvent()。
//IE9+/chrom/FF:addEventListener()和removeEventListener()

Браузеры IE ниже IE9 не поддерживают addEventListener() и removeEventListener(), вместо этого используйте attachEvent() и detachEvent(), поскольку IE9 и ниже не поддерживают захват событий, поэтому третьего параметра перед именем первого события нет. . Это можно сделать для совместимости:

События DOM уровня 3

Добавлено больше типов событий поверх событий уровня DOM 2.

  • События пользовательского интерфейса, срабатывающие, когда пользователь взаимодействует с элементами на странице, например: загрузка, прокрутка

  • Событие фокуса, срабатывающее, когда элемент получает или теряет фокус, например: размытие, фокус

  • События мыши, срабатывающие, когда пользователь выполняет операцию на странице с помощью мыши, например: dblclick, mouseup

  • Событие колеса, срабатывающее при использовании колесика мыши или аналогичного устройства, например: колесико мыши

  • Текстовые события, инициируемые при вводе текста в документ, например: textInput

  • События клавиатуры, срабатывающие, когда пользователь выполняет операцию на странице с помощью клавиатуры, например: нажатие клавиши, нажатие клавиши

  • Событие композиции, инициируемое при вводе символов для IME (редактора метода ввода), например:

  • События изменения, запускаемые при изменении базовой структуры DOM, например: DOMsubtreeModified

  • В то же время события уровня DOM3 также позволяют пользователям настраивать некоторые события.

Суммировать:

  • Преимущество уровня DOM2 заключается в том, что можно добавить несколько обработчиков событий, DOM0 поддерживает только один обработчик для каждого события;

  • Анонимные функции, добавленные через DOM2, нельзя удалить,addEventListenerа такжеremoveEventListenerизhandlerдолжно быть такое же имя

  • Область применения: DOM0.handlerбудет работать в рамках элемента, которому он принадлежит, IEhandlerбудет работать в глобальной области видимости,this === window

  • Порядок срабатывания: при добавлении нескольких событий DOM2 будет выполняться в порядке добавления, IE будет выполняться в обратном порядке, имейте в виду

Кроссбраузерные обработчики событий

совместимыйie9следующие браузеры иDOM0

var EventUtil = {
  // element是当前元素,可以通过getElementById(id)获取
  // type 是事件类型,一般是click ,也有可能是鼠标、焦点、滚轮事件等等
  // handle 事件处理函数
  addHandler: (element, type, handler) => {
    // 先检测是否存在DOM2级方法,再检测IE的方法,最后是DOM0级方法(一般不会到这)
    if (element.addEventListener) {
      // 第三个参数false表示冒泡阶段
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {
      element.attachEvent(`on${type}`, handler)
    } else {
      element[`on${type}`] = handler;
    }
  },

  removeHandler: (element, type, handler) => {
    if (element.removeEventListener) {
      // 第三个参数false表示冒泡阶段
      element.removeEventListener(type, handler, false);
    } else if (element.detachEvent) {
      element.detachEvent(`on${type}`, handler)
    } else {
      element[`on${type}`] = null;
    }
  }
}

// 获取元素
var btn = document.getElementById('btn');
// 定义handler
var handler = function(e) {
  console.log('我被点击了');
}
// 监听事件
EventUtil.addHandler(btn, 'click', handler);
// 移除事件监听
// EventUtil.removeHandler(button1, 'click', clickEvent);

агент событий

Поскольку событие будет распространяться до родительского узла на этапе всплытия, функция прослушивателя дочернего узла может быть определена на родительском узле, а функция прослушивателя родительского узла может единообразно обрабатывать события нескольких дочерних элементов. Этот метод называется делегированием событий (delegation), также известным как делегирование событий. Брокеры событий имеют следующие два преимущества:

  • Сокращение потребления памяти и повышение производительности

Допустим есть список с большим количеством элементов списка, нам нужно отреагировать на событие при клике по каждому элементу списка

// 例4
<ul id="list">
  <li>item 1</li>
  <li>item 2</li>
  <li>item 3</li>
  ......
  <li>item n</li>
</ul>

Если вы привяжете функцию к каждому элементу списка, она будет потреблять много памяти и потреблять много производительности с точки зрения эффективности. С помощью прокси-сервера событий нам нужно только привязать методы к родительскому контейнеру ul, чтобы независимо от того, какой элемент-потомок был нажат, поведение щелчка контейнера будет запускаться в соответствии с механизмом передачи пузырькового распространения, а затем будет выполнен соответствующий метод.В зависимости от источника события мы можем знать, кто нажал, и делать разные вещи.

  • Динамически связывать события

Во многих случаях нам нужно динамически добавлять или удалять элементы списка с помощью пользовательских операций.Если мы привязываем события к каждому дочернему элементу в начале, то при изменении списка нам нужно повторно привязывать события к вновь добавленным элементам, поэтому что элементы, которые собираются удалить, будут связаны с событиями.Элемент отвязывает событие, если вы используете прокси события, вы избавите себя от таких проблем.

Далее давайте реализуем делегирование события элемента li под родительским элементом #list в приведенном выше примере его родительскому элементу:

// 给父层元素绑定事件
document.getElementById('list').addEventListener('click', function (e) {
  // 兼容性处理
  var event = e || window.event;
  var target = event.target || event.srcElement;
  // 判断是否匹配目标元素
  if (target.nodeName.toLocaleLowerCase === 'li') {
    console.log('the content is: ', target.innerHTML);
  }
});

объект события

DOM0а такжеDOM2обработчики событий автоматически передаются вeventобъект, триггерDOMКогда событие включено, будет сгенерирован объект события, который содержит всю информацию, связанную с событием.IEсерединаeventОбъект зависит от метода указанного обработчика событий

IEhandlerбудет работать в глобальной области видимости,this === windowИтак, в IE будетwindow.event,eventдва случая

Также вIE, свойства объекта события также отличаются, и соответствующая связь выглядит следующим образом:

srcElement => target returnValue => preventDefault() cancelBubble => stopPropagation() IEЗахват событий не поддерживается, поэтому всплывающие сообщения о событиях можно только отменить, ноstopPropagationМожно одновременно отменить захват событий и всплывающую подсказку

Только во время обработчиков событий,eventОбъект будет существовать только после того, как обработчик события завершит выполнение,eventобъект будет уничтожен

1. event. preventDefault()

Если этот метод вызывается, поведение события по умолчанию больше не будет срабатывать.. Что такое событие по умолчанию? Например, когда форма нажимает кнопку отправки (отправить), чтобы перейти на страницу, страница тега a по умолчанию перескакивает или точка привязки позиционируется.

Много раз мы используем тег a просто для того, чтобы действовать как обычная кнопка, щелкнуть, чтобы выполнить функцию, не хотим переходить на страницу и не хотим позиционировать привязку.

//方法一:
<a href="javascript:;">链接</a>

Его также можно заблокировать с помощью метода JS и привязать метод к его событию щелчка.Когда мы нажимаем на тег A, сначала будет инициировано событие щелчка, а затем будет выполнено поведение по умолчанию.

//方法二:
<a id="test" href="http://www.cnblogs.com">链接</a>
<script>
test.onclick = function(e){
    e = e || window.event;
    return false;
}
</script>

//方法三:
<a id="test" href="http://www.cnblogs.com">链接</a>
<script>
test.onclick = function(e){
    e = e || window.event;
    e.preventDefault();
}
</script>

Далее рассмотрим пример: в поле ввода можно ввести только до шести символов, как этого добиться?

// 例5
 <input type="text" id='tempInp'>
 <script>
    tempInp.onkeydown = function(ev) {
        ev = ev || window.event;
        let val = this.value.trim() //trim去除字符串首位空格(不兼容)
        // this.value=this.value.replace(/^ +| +$/g,'') 兼容写法
        let len = val.length
        if (len >= 6) {
            this.value = val.substr(0, 6);
            //阻止默认行为去除特殊按键(DELETE\BACK-SPACE\方向键...)
            let code = ev.which || ev.keyCode;
            if (!/^(46|8|37|38|39|40)$/.test(code)) {
                ev.preventDefault()
            }
        }
    }
 </script>

2. event.stopPropagation() & event.stopImmediatePropagation()

event.stopPropagation()метод, чтобы предотвратить всплытие события к родительскому элементу, предотвращая выполнение любых обработчиков родительских событий (обычно мы думаемstopPropagationОна используется для предотвращения всплытия событий, по сути, эта функция также может предотвращать захват событий). Упомянутая выше фаза всплытия события относится к событию снизу вверх целевого узла.windowФаза распространения объекта. В нашем примере вышеinnerэлементclickмероприятие, добавитьevent.stopPropagation()После этого предложения выполнение родительского события блокируется и, наконец, только печатается'inner'.

 inner.onclick = function(ev) {
    console.log('inner');
    ev.stopPropagation();
};

stopImmediatePropagationОба предотвращают всплытие события к родительскому элементу, а также предотвращают запуск других прослушивателей того же типа события для этого элемента. а такжеstopPropagationТолько прежний эффект может быть достигнут. Давайте посмотрим на пример:

<body>
  <button id="btn">click me to stop propagation</button>
</body>
......
const btn = document.querySelector('#btn');
btn.addEventListener('click', event => {
  console.log('btn click 1');
  event.stopImmediatePropagation();
});
btn.addEventListener('click', event => {
  console.log('btn click 2');
});
document.body.addEventListener('click', () => {
  console.log('body click');
});
// btn click 1

Как показано выше, используйтеstopImmediatePropagationПосле этого при нажатии кнопки не толькоbodyСобытие привязки не срабатывает, и в то же время не срабатывает другое событие нажатия кнопки.

3. event.target & event.currentTarget

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

<div id="a">
  <div id="b">
    <div id="c">
      <div id="d"></div>
    </div>
  </div>
</div>
<script>
  document.getElementById('a').addEventListener('click', function(e) {
    console.log(
      'target:' + e.target.id + '&currentTarget:' + e.currentTarget.id
    )
  })
  document.getElementById('b').addEventListener('click', function(e) {
    console.log(
      'target:' + e.target.id + '&currentTarget:' + e.currentTarget.id
    )
  })
  document.getElementById('c').addEventListener('click', function(e) {
    console.log(
      'target:' + e.target.id + '&currentTarget:' + e.currentTarget.id
    )
  })
  document.getElementById('d').addEventListener('click', function(e) {
    console.log(
      'target:' + e.target.id + '&currentTarget:' + e.currentTarget.id
    )
  })
</script>

img

Когда мы нажимаем на самый внутренний элемент d, он последовательно выводит:

target:d&currentTarget:d
target:d&currentTarget:c
target:d&currentTarget:b
target:d&currentTarget:a

Из вывода мы видим,event.targetуказывает на элемент, вызвавший инициирующее событие, иevent.currentTargetЭто элемент, привязанный к событию, только целевой элемент, на который нажали.event.targetбудет равноevent.currentTarget.То есть Event.currentTarget всегда прослушивает событие, а event.target является истинным генератором события..

4. Кроссбраузерные объекты событий

var EventUtil = {
    addHandler: function (el, type, handler) {
        if (el.addEventListener) {
            el.addEventListener(type, handler, false);
        } else if (el.attachEvent) {
            el.attachEvent('on' + type, handler);
        } else {
            el['on' + type] = handler;
        }
    },
    removeHandler: function (el, type, handler) {
        if (el.removeEventListener) {
            el.removeEventListerner(type, handler, false);
        } else if (el.detachEvent) {
            el.detachEvent('on' + type, handler);
        } else {
            el['on' + type] = null;
        }
    },
    getEvent: function (e) {
        return e ? e : window.event;
    },
    getTarget: function (e) {
        return e.target ? e.target : e.srcElement;
    },
    preventDefault: function (e) {
        if (e.preventDefault) {
            e.preventDefault();
        } else {
            e.returnValue = false;
        }
    },
    stopPropagation: function (e) {
        if (e.stopPropagation) {
            e.stopPropagation();
        } else {
            e.cancelBubble = true;
        }
    }
};

Порядок захвата и барботирования

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

1. Зарегистрируйте событие во внешнем div и щелкните внутренний div, чтобы вызвать событие, событие захвата всегда запускается до события всплытия (это не имеет ничего общего с порядком кода).

Допустим, есть такая html-структура:

<div id="test" class="test">
   <div id="testInner" class="test-inner"></div>
</div>

Затем мы регистрируем два события щелчка на внешнем div, а именно событие захвата и событие всплытия Код выглядит следующим образом:

const btn = document.getElementById("test");
 
//捕获事件
btn.addEventListener("click", function(e){
    alert("capture is ok");
}, true);
 
//冒泡事件
btn.addEventListener("click", function(e){
    alert("bubble is ok");
}, false);

Нажмите на внутренний div, сначала захват всплывающего окна в порядке, затем всплывающее окно в порядке. Только когда элемент DOM, который фактически инициирует событие, является внутренним слоем, внешний элемент DOM имеет возможность имитировать событие захвата и событие всплытия.

2. При регистрации события в элементе DOM, который запускает событие, сначала будет выполнено то, что зарегистрировано первым.

Структура html такая же, как и выше, а код js выглядит следующим образом:

const btnInner = document.getElementById("testInner");

//冒泡事件
btnInner.addEventListener("click", function(e){
    alert("bubble is ok");
}, false);
 
//捕获事件
btnInner.addEventListener("click", function(e){
    alert("capture is ok");
}, true);

В этом примере всплывающее событие регистрируется первым, поэтому оно выполняется первым. Итак, нажмите на внутренний div и сначала всплывайтеbubble is ok, затем всплывающее окноcapture is ok.

3. Когда событие захвата зарегистрировано как для внешнего div, так и для внутреннего div, при нажатии внутреннего div событие внешнего div будет запущено первым.

const btn = document.getElementById("test");
const btnInner = document.getElementById("testInner");

btnInner.addEventListener("click", function(e){
    alert("inner capture is ok");
}, true);

btn.addEventListener("click", function(e){
    alert("outer capture is ok");
}, true);

Хотя событие внешнего div регистрируется позже, оно сработает первым. Итак, результат должен появиться первымouter capture is ok, затем всплывающее окноinner capture is ok.

4. Точно так же, когда и внешний div, и внутренний div регистрируют всплывающее событие одновременно, при щелчке внутреннего div событие внутреннего div должно запускаться первым.

const btn = document.getElementById("test");
const btnInner = document.getElementById("testInner");

btn.addEventListener("click", function(e){
    alert("outer bubble is ok");
}, false);

btnInner.addEventListener("click", function(e){
    alert("inner bubble is ok");
}, false);

всплывать первымinner bubble is ok, затем всплывающее окноouter bubble is ok.

5. Предотвратить отправку событий

Обычно мы все хотим нажать наdiv, он запускает только свой собственный обратный вызов события. Например, четкое нажатие на внутренний слойdiv, но внешнийdivСобытие также срабатывает, а это не то, что нам нужно. В это время необходимо предотвратить отправку события.

Когда событие запускается, оно проходит по умолчаниюeventобъект, этоeventНа объекте есть метод:stopPropagation. Объяснение на MDN:Предотвращение дальнейшего распространения текущего события на этапах захвата и всплытия.. Итак, по этому методу пусть внешний слойdivЕсли событие не получено, оно, естественно, не сработает.

btnInner.addEventListener("click", function(e){
    //阻止冒泡
    e.stopPropagation();
    alert("inner bubble is ok");
}, false);

Справочная статья

  1. Расширенное программирование на JavaScript (третье издание)
  2. Разница между event.target и event.currentTarget
  3. JS-события: захват и всплытие, обработчики событий, объекты событий, кроссбраузерность, делегирование событий
  4. Поток событий JavaScript
  5. Механизм событий «Вопросы для интервью, серия 7» в JavaScript (от нативного к фреймворку)
  6. Механизм событий DOM