Event model

Monitor function

The browser's event model is to respond to events through a listener function. After the event occurs, the browser monitors the event and executes the corresponding monitoring function. This is the main programming method of event-driven programming mode.

JavaScript has three methods to bind listener functions for events.

HTML on-attribute

HTML language allows to directly define the monitoring code of certain events in the attributes of the element.

<body onload="doSomething()">
  <div onclick="console.log('Trigger event')"></div>
</body>

The above code is the load event of the body node and the click event of the div node, and the monitoring code is specified. Once the event occurs, this code will be executed.

The event listener attributes of the element are all on plus the event name, for example, onload is on + load, which represents the listener code of the load event.

Note that the value of these attributes is the code that will be executed, not a function.

<!-- Correct-->
<body onload="doSomething()">
  <!-- Error-->
  <body onload="doSomething"></body>
</body>

Once the specified event occurs, the value of the on- attribute is passed to the JavaScript engine for execution. So if you want to execute a function, don't forget to add a pair of parentheses.

The monitoring code specified using this method will only be triggered during the bubbling phase.

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

In the above code, <button> is a child element of <div>. The click event of <button> will also trigger the click event of <div>. Since the monitoring code of the on- attribute is only triggered during the bubbling phase, the result of the click is to output 1 first, and then 2, that is, the event starts to bubble from the child element to the parent element.

Setting the on- attribute directly is the same as setting the on- attribute through the setAttribute method of the element node.

el.setAttribute("onclick", "doSomething()");
// Equivalent to
// <Element onclick="doSomething()">

Event attributes of element nodes

The event attribute of the element node object can also specify the listener function.

window.onload = doSomething;

div.onclick = function (event) {
  console.log("Trigger event");
};

The listener function specified by this method will only be triggered during the bubbling phase.

Note that the difference between this method and HTML's on- attribute is that its value is the function name (doSomething), unlike the latter, the complete monitoring code (doSomething()) must be given.

EventTarget.addEventListener()

All DOM node instances have the addEventListener method, which is used to define event listener functions for the node.

window.addEventListener("load", doSomething, false);

For a detailed introduction of the addEventListener method, please refer to the EventTarget chapter.

Summary

The above three methods, the first "on-attribute of HTML", violates the principle of separating HTML and JavaScript code. Writing the two together is not conducive to code division, so it is not recommended.

The disadvantage of the second type of "event attributes of element nodes" is that only one listener function can be defined for the same event, that is, if the onclick attribute is defined twice, the latter definition will overwrite the previous one. Therefore, it is not recommended.

The third type of EventTarget.addEventListener is the recommended way to specify the listener function. It has the following advantages:

-Multiple listener functions can be added to the same event. -Ability to specify in which phase (capture phase or bubbling phase) the listener function will be triggered. -In addition to DOM nodes, other objects (such as window, XMLHttpRequest, etc.) also have this interface, which is equivalent to a unified monitoring function interface for the entire JavaScript.

The point of this

The this inside the listener function points to the element node that triggered the event.

<button id="btn" onclick="console.log(this.id)">click</button>

Execute the above code, after clicking it, btn will be output.

The other two kinds of listening functions are written in the same way as for this.

// HTML code is as follows
// <button id="btn">click</button>
var btn = document.getElementById("btn");

// Writing method one
btn.onclick = function () {
  console.log(this.id);
};

// Writing method two
btn.addEventListener(
  "click",
  function (e) {
    console.log(this.id);
  },
  false
);

In the above two ways, after clicking the button, btn is also output.

The spread of the event

After an event occurs, it will propagate between the child element and the parent element. This spread is divided into three stages.

-The first stage: From the window object to the target node (upper layer to the bottom layer), called the "capture phase". -Second Phase: Triggered on the target node, called the "target phase". -The third stage: The window object is transmitted back from the target node (from the bottom layer to the upper layer), which is called the "bubbling phase".

This three-stage propagation model allows the same event to be triggered on multiple nodes.

<div>
  <p>Click</p>
</div>

In the above code, there is a <p> node among the <div> nodes.

If you set up a listener function for the click event for both nodes (one listener function is set for the capture phase and bubbling phase of each node), a total of four listener functions are set. Then, when you click on <p>, the click event will be triggered four times.

var phases = {
  1: "capture",
  2: "target",
  3: "bubble",
};

var div = document.querySelector("div");
var p = document.querySelector("p");

div.addEventListener("click", callback, true);
p.addEventListener("click", callback, true);
div.addEventListener("click", callback, false);
p.addEventListener("click", callback, false);

function callback(event) {
  var tag = event.currentTarget.tagName;
  var phase = phases[event.eventPhase];
  console.log("Tag:'" + tag + "'. EventPhase:'" + phase + "'");
}

// Click on the future result
// Tag:'DIV'. EventPhase:'capture'
// Tag:'P'. EventPhase:'target'
// Tag:'P'. EventPhase:'target'
// Tag:'DIV'. EventPhase:'bubble'

The above code indicates that the click event was triggered four times: once in the capture phase and bubbling phase of the <div> node, and the target phase of the <p> node was triggered twice.

  1. Capture phase: when the event propagates from <div> to <p>, the click event of <div> is triggered;
  2. Target stage: when the event reaches from <div> to <p>, the click event of <p> is triggered;
  3. Bubbling stage: When the event returns from <p> to <div>, the click event of <div> is triggered again.

Among them, the <p> node has two listener functions (the third parameter of the addEventListener method is different, which will cause two listener functions to be bound), so they will all be triggered once because of the click event. Therefore, <p> will have two outputs in the target phase.

Note that the browser always assumes that the target node of the click event is the node with the deepest click position nested (in this example, the <p> node in the <div> node). Therefore, the capture phase and bubbling phase of the <p> node will be displayed as the target phase.

The top-level object for event propagation is window, followed by document, html (document.documentElement), and body (document.body). In other words, the sequence of event propagation in the above example is window, document, html, body, div, p in the capture phase, and p, div, body, html, document, window.

Event Agent

Since the event will propagate up to the parent node during the bubbling phase, the listener function of the child node can be defined on the parent node, and the listener function of the parent node will uniformly handle the events of multiple child elements. This method is called delegation of events.

var ul = document.querySelector("ul");

ul.addEventListener("click", function (event) {
  if (event.target.tagName.toLowerCase() === "li") {
    // some code
  }
});

In the above code, the listener function of the click event is defined in the <ul> node, but in fact, it handles the click event of the child node <li>. The advantage of this is that as long as you define a listener function, you can handle the events of multiple child nodes, instead of defining a listener function on each <li> node. And if you add child nodes later, the monitoring function is still valid.

If you want the event to reach a certain node and no longer propagate, you can use the stopPropagation method of the event object.

// After the event is propagated to the p element, it will no longer propagate down
p.addEventListener(
  "click",
  function (event) {
    event.stopPropagation();
  },
  true
);

// After the event bubbled to the p element, it no longer bubbled upwards
p.addEventListener(
  "click",
  function (event) {
    event.stopPropagation();
  },
  false
);

In the above code, the stopPropagation method prevents the event from being propagated during the capture phase and the bubbling phase.

However, the stopPropagation method will only prevent the propagation of the event, and will not prevent the event from triggering the listener functions of other click events of the <p> node. In other words, it does not completely cancel the click event.

p.addEventListener("click", function (event) {
  event.stopPropagation();
  console.log(1);
});

p.addEventListener("click", function (event) {
  // will trigger
  console.log(2);
});

In the above code, the p element is bound to two listening functions for the click event. The stopPropagation method can only prevent the propagation of this event, and cannot cancel this event. Therefore, the second listener function will be triggered. The output will be 1 first, then 2.

If you want to cancel the event completely and no longer trigger all subsequent click monitoring functions, you can use the stopImmediatePropagation method.

p.addEventListener("click", function (event) {
  event.stopImmediatePropagation();
  console.log(1);
});

p.addEventListener("click", function (event) {
  // will not be triggered
  console.log(2);
});

In the above code, the stopImmediatePropagation method can completely cancel this event, so that all click listener functions bound later will no longer be triggered. Therefore, only 1 will be output, and 2 will not be output.