The reason

Previous learning mechanism in natural events, once-over and then skip this part of the study, later found a fun event mechanism demonstration site, find previously know too shallow, often a man who knows one second, you need to review, so write this article as a record summary, also wants to help the new entry-level front classmate little detours, One article combs good relevant knowledge point.

What is the event

Client-side JavaScript programs use an asynchronous event-driven programming model, in which the browser generates events when something of interest happens to a document, the browser, or some element or object associated with it

In JavaScript’s Definitive Guide, the events described above may be confusing to read, but here are a few 🌰 : An onLoad event is emitted when the browser loads the entire page, an onClick event is emitted when the user clicks on an element, and an onclick event is emitted when the user stretches the browser or hits the keyboard. In other words, the browser waits for the user to interact with it and then responds. We care about these user actions happening, and the browser creates a “checkpoint” for all of these noteworthy events, allowing programmers to “intercept” events when they are triggered, obtaining the necessary event details to do something in response.

Introduction to related terms

This paragraph provides a brief explanation of terms and definitions. To ensure fluency and a better reading experience, you are advised to review or skip the text and come back to it when you are confused.

The event type

The event type is a string that represents what happened.

Common examples include click, mousemove, and so on, which are used to describe specific user actions and are sometimes called event names because the event type is a string.

Event goals

An event target is an object on which the event occurs or is associated.

Window, Document, and Element elements should be the most common event targets, such as the load on the Window and the click event, but there are other event targets. For example, the worker object is the target of message and occurs when the worker thread sends a message to the main thread.

Event listener

An event listener is a function that handles or responds to events.

The program registers its own event handler through the browser, specifying the event type and event target. When an event of a specified type occurs on an event target, the browser applies its own rules and invokes an event handler, generally referred to as the browser “firing,” “dispatching,” or “dispatching” the event.

The event object

An event object is an object associated with a particular event and contains details about the event, which will be referred to simply as “event” in the following paragraphs.

Event objects are passed in as arguments to event listeners, and all event objects have type and target attributes that represent the event type and event target, respectively. Different events have a unique set of properties, such as a mouse event that has the coordinates of the mouse pointer, and a keyboard event that contains information about the key that was pressed and the modifier key that was held. Many other event types define only a few standard attributes (including type and target), and for these types of events, it’s important to know that it happened, not the details of the event.

Event propagation

Event propagation is a process in which the browser decides which objects trigger event handlers.

For one object specific events such as a load on a Window object or a Message on a Worker object, no propagation is required.

For certain events in HTML documents, however, there are three stages:

  1. The process is called the capture phase, which starts with the Window object and ends with the event target.
  2. It then dispatches events on the target of this event, referred to here as the target phase
  3. The Window object is then fired from the target element in a process called the “bubbling phase,” during which event listeners are fired by default.

If you find the text confusing, take a look at the illustration below, or click on this link to see how it works.

Some events have default actions associated with them. For example, clicking on the hyperlink will cause the browser to follow the link and load a new page, and the event listener can call event.preventDefault() in the event object to cancel the default action.

Register event methods

DOM events are divided into three levels: DOM0, DOM2, and DOM3

DOM0 level events

Dom0-level events, which register events directly on event targets, are an early browser method of registering events. It is still available but should be phased out. This article introduces dom0-level events, but it is highly recommended.

Example Code 1
<! Register events for BTN -->
<button onclick="alert(123)"></button>

<! Get event object, note that only event can be used as event object variable -->
<button id="btn" onclick="console.log(event)"></button>

<! -- Register multiple events for BTN, with; Separate - >
<button onclick="alert(123); handleClick()"></button>

<! -- Cancel the default behavior of a tag -->
<a href="https://domevents.dev/" onclick="return false">https://domevents.dev/</a>
Copy the code

The above code is easy to pollute HTML documents, high degree of coupling, is absolutely a nightmare for your site, not recommended! Separating code logic from content also makes your site more search engine friendly.

Example Code 2
const btn = document.getElementById('btn')
btn.onclick = function(event){
  // Outputs the event object
  console.log(event)
  
  // Prevents further propagation of current events in the capture and bubble phases.
  // However, it does not prevent any default behavior from occurring; Clicks on links, for example, will still be processed
  event.stopPropagation()
  
  // Prevent other event listeners listening to the same event from being called.
  If multiple event listeners are attached to the same event type of the same element, when the event is triggered, they are called in the order in which they were added.
  // If you execute stopImmediatePropagation() in one of the event listeners, the rest of the event listeners will not be called.
  event.stopImmediatePropagation()
  
  // Default actions should not be executed as usual
	event.preventDefault()
  
	// Cancel the default behavior the second method is not recommended
  // return false
}
Copy the code

To register the event logic, just assign the corresponding attribute method. Compared to the previous code, the coupling degree is reduced, but there is a problem that the latter will override the former if you want to bind the same type of event to the same target element. Of course, we could wrap a function of our own that contains all the events that need to be overridden in sequence, but that would still be more cumbersome. It is recommended only in certain situations, such as when better cross-browser compatibility is required. But now that Internet Explorer is no longer supported, we should look to the future and use better DOM2 level events!

As you can see in the return false on the last line, example code 2 and example code 1 are actually the same, and example code 1 is a simplified version of example code 2. The former can be understood in the following form.

functino(event) {
  with(document) {
    with(this.form || {}) { // if there is 
      
with(this) { // your code}}}}Copy the code
Grade DOM2 events

In short, there are several new apis. One is: EventTarget. AddEventListener (eventName, callback, the options), any object can be used as an event target, defines the method that can use it to register the target object to invoke the event handler.

It accepts three parameters:

  1. Event name: Note that it does not contain the prefix “on”
  2. Callback functions: by name, not by description
  3. Configuration item: This parameter is optional. It is used to control the trigger time
    The sample code
    const btn = document.querySelector('#btn')
    btn.addEventListener('click'.(event) = > {
      console.log('capture');
    }, true)
    
    btn.addEventListener('click'.(event) = > {
      // Bubble phase
      console.log('bubble1')
      // this points to BTN
      console.log(this);
      
      // Get the event object
      console.log(event);
    }, false) 
    Boolean controls when the event listener is fired. False indicates that the event listener is fired during the bubbling phase, or the capture phase
    // Default is false
    
    btn.addEventListener('click'.(event) = > {
      // Bubble phase
      console.log('bubble2')},false) 
    
    
    // The result is as follows when BTN triggers click
    
    // capture
    // bubble1
    // bubble2
    // thisObj
    // eventObj
    Copy the code

    It is important to note that addeventListeners do not overwrite each other, but execute from top to bottom in the order in which the code is written. For target elements, the capture phase is followed by the bubbling phase. For those of you who may be confused about the sequence of code execution in the target phase 🤔, here is an article that should clear your mind. Click here.

    When writing the code, try to make sure to write the code of the capture phase first, then write the code of the bubble event, so as to ensure that the code from top to bottom, there is no need to consider too many problems.

    The third argument to addEventListener can also be passed in an object that looks like this:

    {
      capture: false.once: false.passive: false
    }
    Copy the code

    Capture is used to determine the timing of event listeners.

    Once is understood to indicate that the event listener is called at most once and is removed after being called.

    If passive is true, it will never call preventDefault(), but there is no guarantee that programmers won’t add it to their code. If passive is true and preventDefault() is still called, A warning is thrown on the console.

    If you don’t understand why it should never call preventDefault(), you’ll find out later.

    If the event listener is removed, for DOM0-level events, you only need to delete the response code or set the corresponding writing method to undefined. For Dom2-level events, Use the EventTarget. RemoveEventListener (eventName, callback [options]).

    Obviously, you can remove the event listener simply by providing the same eventName and callback, but what about using a third optional parameter? Here’s an example: 🌰

    EventTarget.addEventListener("mousedown", handleMouseDown, true);
    
    // Now consider the following two removeEventListener(). Which one will be successfully removed?
    EventTarget.removeEventListener("mousedown", handleMouseDown, false);    
    EventTarget.removeEventListener("mousedown", handleMouseDown, true);     
    Copy the code

    The second answer is that when removeEventListener is used, it checks for the Capture flag, which must match the previous addEventListener.

    Now look at the following example:

    EventTarget.addEventListener("mousedown", handleMouseDown, { passive: true });
    
    EventTarget.removeEventListener("mousedown", handleMouseDown, { passive: true });     / / success
    EventTarget.removeEventListener("mousedown", handleMouseDown, { capture: false });    / / success
    EventTarget.removeEventListener("mousedown", handleMouseDown, { capture: true });     / / fail
    EventTarget.removeEventListener("mousedown", handleMouseDown, { passive: false });    / / success
    EventTarget.removeEventListener("mousedown", handleMouseDown, false);                 / / success
    EventTarget.removeEventListener("mousedown", handleMouseDown, true);                  / / fail
    Copy the code

    Removal is successful only when Capture is true, so only capTrue affects removeEventListener.

    So if the same listener event is registered once for “capture” and once for “bubble”, we need to remove them separately so that they do not interfere with each other.

    tips

    When an EventListener on an EventTarget is removed, it stops immediately if the event is being executed.

    There is one last API left, dispatchEvent, which is less used and takes an event object that sends an event to a specified target element and synchronously calls the event handlers associated with the target element in the appropriate order. Note that this synchronous call is different from native events, which are dispatched by the DOM and asynchronously invoke the handler via event-loop. After calling dispatchEvent, all event handlers that listen for the event will continue in the code and return.

    The sample code
    const btn = document.querySelector('#btn')
    btn.addEventListener('click'.(event) = > {
      console.log('bubble')})// There seems to be no problem
    btn.dispatchEvent('click')
    Copy the code

    But the actual operation has an error:

    The reason is that the parameter type is not Event, so I’ll have to work a little harder to change the sample code.

    const btn = document.querySelector('#btn')
    btn.addEventListener('click'.(event) = > {
      console.log('event', event);
    })
    New Event(type, options)
    // It takes two arguments:
    // -type 
            
              Event name
            
    // -options  Optional configuration item object
    // -bubbles < Boolean > indicates whether the event bubbles
    // -cancelable < Boolean > Indicates whether the event can be cancelled
    // -composed < Boolean > specifies whether an event triggers a listener outside of the shadow DOM root node
    // See the following example to understand how to use it
    const clickEvent = new Event('click', { 
      bubbles: true 
    });
    setTimeout(() = > {
      btn.dispatchEvent(clickEvent)
    }, 1000)
    Copy the code

    Everything is fine, no errors are reported, but the event object output via dispatchEvent has a lot less properties. Look at __proto__ on the last line, and you get something.

    It turns out that the prototype of the Event object is inconsistent. MouseEvent has more attributes than Event. If you want dispatchEvent to get MouseEvent, you have to change the original sample code again. If you change new Event() to new MouseEvent(), you’ll notice that there are the same number of arguments, but there are still some exceptions. Many of the arguments are changed to 0 or false

    But you can also quickly figure out, since the code triggered the click rather than the user, how can there be information only when the click? This information becomes the default value. What if we wanted to set parameters to simulate real events? Clicking on the top, bottom, left, and right corners of a div, for example, sounds a little “unreasonable,” but it does satisfy that “unreasonable” need. Welcome to CustomEvent!

    CustomEvent is used in much the same way as the previous events, except for its second parameter:

    {
    	detail<any>, // That's the point
    	bubbles<boolean>,
    	cancelable<boolean>
    }
    Copy the code

    We are already familiar with bubbles && Cancelable. What is the detail? Here’s a sample code to illustrate:

    const btn = document.querySelector('#btn')
    btn.addEventListener('click'.(event) = > {
      console.log('event', event);
    })
    
    const clickEvent = new CustomEvent('click', {
      detail: {
      	pageX: '15px'
      },
    	someOfOthers: {
      	sth: 1}}); btn.dispatchEvent(clickEvent)Copy the code

    We found an extra detail property that contains the information we added. This solves the problem of customizing various detailed events.

    Level the DOM3 event

    DOM3 adds more event types to DOM2 and also allows developers to customize some events. Here is a brief introduction.

    • UI events that are triggered when the user interacts with elements on the page, such as Load and Scroll

    • Focus events, which are triggered when an element gains or loses focus, such as blur and focus

    • Mouse events, such as dblclick and mouseup, are triggered when the user performs actions on the page using the mouse

    • A wheel event that is triggered when a mousewheel or similar device is used, such as mousewheel

    • A text event that is triggered when text is entered into a document, such as textInput

    • Keyboard events, such as keyDown and keyPress, are triggered when users perform operations on the page using the keyboard

    • A composite event that is triggered when a character is entered for the IME, such as compositionStart

    • Change event, triggered when the underlying DOM structure changes, such as DOMsubtreeModified

    The business scenario

    Said so much, or to implement the business, take a look at the business scenario.

    Event delegation

    When we bind events to a single target element, we just shuttle the addEventListener. What about 100, 1000 target elements? Will there be any problems? Sure, we can easily do 😉 with a for loop, but not with a browser. We know that objects take up memory, and every function is an object, so if you have to bind events to 1000 target elements, memory consumption is much higher.

    We can use the Event bubbling principle, for the outer element binding corresponding Event type, Event trigger is the inner elements of the response will bubble to the outer layer, now the only problem is to determine what a child element is triggering Event, fortunately, the Event object to give us a target attribute, and allows us to quickly locate the target element. Sample code for the following case:

     <ul>
        <li id="li1">1</li>
        <li id="li2">2</li>
        <li id="li3">3</li>
        <li id="li4">4</li>
      </ul>
    Copy the code
    const ul = document.querySelector('ul')
    ul.addEventListener('click'.event= > {
      console.log('target', event.target);
    })
    Copy the code

    The above is the feedback effect of clicking on various parts of ul. Here, it is easy to control the desired result through target.

    Don’t be in a hurry to use it, event delegate also has its natural defects, based on the bubbling principle, it is bound to be limited to bubbling, for some non-bubbling events, such as Focus && Blur, etc., can not be used. And suggest nearby delegate, otherwise may be prevented by a certain layer.

    PASSIVE

    PreventDefault () is never called when it is set to true. If the listener is passive, preventDefault() is never called.

    According to the specification, the default value for passive is always false, but this introduces the possibility that event listeners that handle certain touch events (and others) block the browser’s main thread while trying to handle scrolling, which can lead to a significant performance degradation during scrolling

    Read the text may not be easy to understand, here is a video, I believe that after watching the chest, point this (need science online).

    As we swipe, the browser doesn’t know whether preventDefault is called or not, so it’s running the event listener. This process usually takes a while, and the browser waits for the event listener to complete before sliding, causing the user experience to be silkier. For these event listeners that may affect the user experience, you need to set passive to true to notify the browser to skip the check and allow the main thread.

    You don’t need to be careful about the passive of the Scroll event. Event listeners cannot prevent page rendering because they cannot be cancelled.

    Now, even if you write an infinite loop in the event listener, the browser can handle the page sliding properly!

    Current browsers are generally compatible with passive, but you can also use the following code to check that the browser correctly recognizes passive

    // Test via a getter in the options object to see if the passive property is accessed
    var supportsPassive = false;
    try {
      var opts = Object.defineProperty({}, 'passive', {
        get: function () {
          supportsPassive = true; }});window.addEventListener('testPassive'.null, opts);
      window.removeEventListener('testPassive'.null, opts);
    } catch (e) {}
    Copy the code

    End and spend

    That concludes our introduction to browser events, but hopefully this article has given the reader a little more insight into events. Of course, there must be some flaws in this article, please comment in the comments section!

    reference

    [0] The Definitive Guide to JavaScript

    [1] Chrome 89 update event trigger sequence

    [2] passive

    [3] Mobile Web Interface rolling performance optimization: Passive Event Listeners

    [4] JavaScript event mechanism