Deep Reading Records of JavaScript Ninja Secrets (part 1)
An event that has endured
13.1 Dive into the Event Loop
For starters, an event loop contains not just event queues, but at least two queues that hold other operations performed by the browser in addition to events. These operations are called tasks and fall into two categories: macro tasks and micro tasks.
There are many examples of macro tasks, including creating a master document object, parsing HTML, executing mainline (or global) JavaScript code, changing the current URL, and various events such as page loading, input, network events, and timer events. From the browser’s perspective, macro tasks represent discrete, independent units of work. After running the task, the browser can proceed with other scheduling, such as rerendering the page’s UI or performing garbage collection.
Microtasks are smaller tasks. Microtasks update the state of the application, but must be performed before the browser tasks continue to perform other tasks, including rerendering the UI of the page. Examples of microtasks include promise callbacks, DOM changes, and so on. Microtasks need to be executed as quickly and asynchronously as possible without creating entirely new microtasks. Microtasks enable us to perform the specified behavior before rerendering the UI, avoiding unnecessary UI repainting that can discontinuous the state of the application.
Event loops are based on two basic principles:
- Tackle one task at a time
- Once a task is started, it will not be interrupted by other tasks until it is finished
Event loops typically require at least two task queues: macro task queues and microtask queues, both of which execute only one task at a time.
Globally, in an iteration above, the event loop will first check the macro task queue and execute it immediately if the macro task is waiting. Until the task completes, the event loop moves to process the microtask queue. If there are tasks waiting in the queue, the event loop will start executing one by one, and then execute the remaining microtasks until all the microtasks in the queue are completed. Note the difference between processing macro tasks and microtask queues: in a single iteration of the loop, at most one macro task is processed (the rest wait in the queue), while all microtasks in the queue are processed.
When the microtask queue processing is complete and empty, the event loop checks to see if the UI rendering needs to be updated and, if so, rerenders the UI view. At this point, the current event loop ends, and then you go back to the original first step, check the macro task queue again, and start a new event loop.
-
Both types of task queues are independent of the event loop, which means that the addition of the task queue also occurs outside the event loop. Failure to do so will result in any events that occur when Js code is executed being ignored. Because we don’t want this, the behavior of detecting and adding tasks is done independently of the event loop.
-
Because Js is based on a single-threaded execution model, both types of tasks are executed one by one. When a task is executed, it is not interrupted by any other task until it is completed. Unless the browser decides to abort the task, for example, if a task takes too long to execute or consumes too much memory.
-
All microtasks are executed before the next render because their goal is to update the application state before rendering.
13.1.2 Examples of both macro and micro tasks
<button id="button1">button1</button> <button id="button2">button2</button> <script> document.querySelector('#button1').addEventListener('click', () => { console.log('button1 click') console.time() for(let i = 0; i < 10000000000; i++ ) {} new Promise(resolve => { resolve('hello result') }).then(res => { console.timeEnd() console.log(res) }) }) document.querySelector('#button2').addEventListener('click', () => {console.log('button2 click')}) // Click button1 and button2Copy the code
Let’s click button1 and button2 in quick sequence
The output is:
-
First, the macro task queue main JS is executed. During the execution of the main thread JS, two buttons are clicked. The click events of these two buttons are in the waiting state in the macro task queue, and the microtask queue is empty at this time
-
After the main thread code runs, each time a macro task is completed and executed, the event loop checks the microtask queue and renders the page as needed if the microtask queue is empty.
-
Then execute the first click event (click button1), and the running time of the for loop in button1 is about 10s. After the for loop in button1 click event ends, a micro-task is added to process the promise success (now the second click event is waiting for execution). In order of precedence, the second click event should be executed first. But as we mentioned earlier, microtasks have priority, and we see that the event loop always checks the microtask queue first, with the goal of completing all the microtasks before processing any other tasks.
That’s why: The promise object’s success callback is executed immediately after the first click event completes, while the second click event, which was in the queue earlier, continues to wait.
When the microtask processing is complete, the page can be re-rendered if and only if there are no microtasks waiting in the microtask queue. In our example, when the Promise processor finishes running, the browser can rerender the page before the second button clicks the processor to execute.
13.2 Play timer: delay execution and interval execution
Timers are often misused and are a poorly understood JavaScript feature that, when used properly, can help develop complex applications. A timer can delay the execution of a piece of code for at least a specified length of time in ms.
First, let’s look at the function created to control the timer. Browsers provide two methods for creating timers: setTimeout and serInterval. The browser also provides two corresponding methods to clear timers: clearTimeout and clearInterval. These methods are all methods mounted on the Window object (global context). These methods are not defined by Js itself, but are provided by the host environment (such as a browser or Node.js).
Note: It is important to understand that there is no guarantee of how long the timer delayer is delayed.
13.2.1 Executing a timer in an event loop
<button ID ="testButton"></button> <script> setTimeout(() => {// handle events 6ms}, 10) setInterval(() => {// handle events 10ms}, 10) const testButton = document.getElementById('testButton') testButton.addEventListener('click', () => {// handle events 10ms}) </script>Copy the code
Now assume that the code block in this example needs to run for 18ms, and assume that a user clicks the button quickly at 6ms.
The first task in the queue is to execute the main thread js code, which runs for 18ms. During implementation, three important events occurred:
-
At 0ms, the delay timer is executed 10ms late, and the interval timer is executed 10ms apart. The reference to the timer is saved in the browser.
-
Click at 6ms
-
When the delay timer expires at 10ms, the first interval of the interval timer fires
Note: If the interval event is triggered and a corresponding task is already in the queue, no new task will be added. Otherwise, no processing is done, as shown in 20ms and 30ms queues.
Because of the single-threaded nature of Js, we can only control when timers are enqueued, not when they are executed.
The difference between delayed execution and interval execution
At first glance, interval execution looks like a periodic repetition of delayed execution. But the differences don’t stop there.
SetTimeout (function repeatMe() {setTimeout(repeatMe, 10)}); Run a task every 10ms setInterval(() => {// do something})Copy the code
Two pieces of code appear to be functionally equivalent, but they are not. Obviously, the code in setTimeout will wait at least 10ms after the previous callback completes (depending on the state of the event queue, the wait time can only be greater than 10ms). SetInterval tries to execute the callback every 10ms, regardless of whether the previous callback was executed.
13.3 Event Handling
The W3C committee set up a standard that includes both approaches and is implemented by all modern browsers. An event can be handled in two ways:
- Capture: First caught by the top element and passed down in turn
- Bubble: After the target element is captured, the event processing shifts to bubble, bubbling from the target element to the top element
We can pass parameters to addEventListenner and easily select the desired event processing order. If true is passed, event capture is used, if false, event bubbling is used. So in a sense, the W3C standard favors event bubbling.
Note: e.target refers to the object to which the event occurred, and this generally refers to the object to which the event is bound (except for arrow functions).
13.4 summary
- Event loop tasks represent actions performed by the browser. Tasks fall into two categories.
- Macro tasks are discrete, independent browser operations, such as creating the main document object, handling various events, changing urls, and so on
- Microtasks are tasks that should be performed as soon as possible. This includes promise callbacks and DOM mutations
- Due to the single-threaded execution model, only one task can be processed at a time, and the execution of one task cannot be interrupted by another. Event loops typically have at least two event queues: macro task queues and microtask queues.
- Asynchronous timers provide the ability to delay the execution of a piece of code for at least a specified number of milliseconds
- Use the setTimeout function to execute the callback after the specified delayed event
- Use the setInterval function to start a timer that will attempt to execute a callback at the specified delay interval until it is cleared.
- Both functions return the timer ID. With the clearTimeout and clearInterval functions, we can use the timer ID to cancel the timer.
- DOM is a hierarchical tree of elements, and events that occur on an element are usually brokered through the DOM, with the following two mechanisms:
- Event capture mode: Events are passed down from the top element to the target element
- Event bubble mode: Events bubble up from the target element to the top element