DOM events

What are DOM events

DOM events are not a JavaScript feature, but are generated by the browser, and DOM gives JavaScript the ability to react to events on HTML, including mouse clicks, scrollbars, changes in page content, and so on.

Monitoring function

How does the DOM model respond to events?

The browser’s DOM event model reacts to events through listener functions. If a listener is listening to the event, it executes the corresponding listener function.

Listen for the binding method of the function

Through HTML property binding

HTML allows you to define listener functions in an element’s attributes.

or

The element’s event listener attribute is on plus the event name. Listener functions bound with this method are only fired during the bubble phase.

<div onclick="console.log(2)">
  <button onclick="console.log(1)">Click on the</button>
</div>
Copy the code

Because listeners via element attribute bindings only fire during the bubble phase, click button to perform the inner console first, then the console of the parent element.

console:

1

2

Binding via the event attribute of the element node

window.onload = doSomething  // There is no need to write doSomething(), just write the function name, unlike HTML attributes, which require full code

div.onclick = function(e){
    console.log('Trigger click event')}Copy the code

Listeners bound to this method also fire only during the bubble phase.

Bind via the addEventListener method

window.addEventListener('load,doSomething,false')

All DOM nodes have an addEventListener method that takes three arguments.

  • type: Event name, case sensitive.
  • listener: Listener function. This listener function is called when an event occurs.
  • useCapture: Boolean value if set totrue, indicating that the listener function will fire during the capture phase. This parameter is optional. The default value isfalse(Listeners are only fired during the bubble phase).

Examples of usage:

function hello() {
  console.log('Hello world');
}

var button = document.getElementById('btn');
button.addEventListener('click', hello, false);
Copy the code

Summary of event binding

Using HTML attribute binding violates the idea of separating HTML and JavaScript code and is not recommended. The disadvantage of attribute binding via element nodes is that only one listener can be bound to an event, such as defining the onclick attribute twice, which overrides the previous one and is not recommended. It is recommended to use the addEventListener function to bind listener functions because:

  • Multiple listener functions can be bound to the same event
  • You can manually specify whether to fire listeners during the capture phase or the bubble phase

Transmission of events

Event capture and event bubbling

The event is propagated between the child and parent elements. We divide this process into three stages. The first stage is to find the listener function from the window object to the target node (from outside to inside), which is called the “capture stage”. The second stage is the event triggering stage; The third stage is to look for listeners from the target node to the window object (inside and out) and is called the “bubbling stage.”

The W3C has published standards stating that browsers should support both call orders, first looking for listeners in the event capture path and then looking for listeners in the event bubble path.

Event bubbling can be cancelled

Capture cannot be cancelled, but bubbling can.

E.toppropagation () can interrupt bubbles and is generally used to encapsulate some independent components.

Event default behavior

The event default behavior includes:

  • The href of the a tag automatically jumps
  • Type =submit The default form submission
  • Other browser default behavior

PreventDefault () is used when we don’t want the default behavior of these events to take place, for example, if we don’t want to skip to the link when we click on it.

<a href="http://baid.com">baidu</a> <script> document.querySelector('a'). Onclick = function(e){// Block default event E.preventdefault () // Click without redirecting to the link address, Instead, print the link console.log(this.href) on the console if(/baidu.com/.test(this.href)){location.href = this.href}} </script>Copy the code

Default behavior of events that cannot be prevented

The event has the following properties:

  • Bubbles indicates whether or not Bubbles
  • Cancelable indicates whether the default behavior of the event can be blocked

If the Cancelable attribute of the event is NO, the default action of the event cannot be blocked. For example, the scroll event, preventing the default scroll action (scroll) is useless, because the scroll event comes first. To prevent scroll, you can only prevent the default action of wheel and touchstart, but the scroll bar still works, you can also use CSS to make the scroll bar width:0. It is also possible to unscroll the scrollTop directly with CSS overflow: Hidden, but JS can still modify the scrollTop.

Event delegation

If there are many buttons in a div and each button is bound to an event (button.addeventListener ()), it will waste a lot of memory space and DOM operation is very slow. We can bind listeners to the ancestor div element. Entrust div to help us determine whether the user is manipulating a button, and execute the corresponding event if it is. We call this design approach of binding listeners to the ancestor of the target element an event delegate.

div.addEventListener('click'.(e) = >{
	const t=e.target
  if(t.tagName.toLowerCase()==='button') {console.log('button is click')}})Copy the code

The principle of

Event delegation is implemented using the bubbling principle of events. For example, if you have a tree of nodes, div>ul> Li >a, add a click event to the innermost element of a, and the event will be executed layer by layer in the order a> Li >ul>div. There is a mechanism for adding a click event to the outermost element of a div, and ul, Li, When a does click events, they all bubble up to the outermost div, so they all fire, and that’s the event delegate, and they delegate the event to their parent.

advantages

  • Save memory
  • Reduce DOM manipulation and increase runtime speed
  • Can listen for dynamic elements (elements that do not currently exist)

Encapsulating event delegate

By encapsulating the event delegate code as a shared function, we can bind any event listener function to any descendant of any element, greatly saving code.

Example: on(‘click’,’#testDiv’,’li’,’fn’), when the user clicks on the li element in #testDiv, the fn function is called.

This public function takes four arguments:

  • [string] eventType eventType
  • [string] The target object of the element proxy binding, that is, some parent object of the element that the user is currently manipulating
  • [string] Seletor has descendant elements that need to be propped for events
  • [Function] fn Function that needs to respond when an event is listened on
on('click'.'#testDiv'.'li',fn)
// When the user clicks the li element in #testDiv, the fn function is called.
function on(eventType,element,seletor,fn){
    if(! (elementinstanceof Element)){
    element = document.querySelector(element)
}
element.addEventListener(eventType,(e) = >{
   const t = e.target
    if(t.matches(selector)){
    fn(e)
     }
  })
}
Copy the code

Train of thought

Add a listener to the ancestor element to determine if the current target matches the selector. If so, fn is executed. If not, skip.

Existing problems

If target changes from button to span when there is a span element in a button, t.matches(selector) is false, so the event listener is disabled.

Event delegate encapsulation improvements

function(eventType,element, selector, fn) {
    if(! (elementinstanceof Element)){
    element = document.querySelector(element)
}
    element.addEventListener(eventType, e= > {
      let el = e.target
      while(! el.matches(selector)) {if (element === el) {
          el = null
          break
        }
        el = el.parentNode // Step up to find if the ancestor node has a node that matches the selector
      }
      el && fn.call(el, e, el)
    })
    return element
  },
Copy the code

In this way, the above problems will not occur. First, determine whether the element of the current operation of the element matches button; if not, determine whether its parent element matches button; if the matched parent element is found, call FN; if the top-level element (element) is still not found, the process ends.