Look at this article with questions in mind

  • When are the various callbacks we write executed? In what order?
  • SetTimeout (cb,0) and promise.resolve ().then(cb) whose callback executes first?
  • How does a single thread of Javascript enable asynchronous concurrency?
  • How exactly does Event Loop schedule tasks?
  • How can RAF be used to optimize performance?
  • What is the output of this code? If the answer is wrong, you may have a clear idea after reading this article
console.log(1);
setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});
new Promise((resolve, reject) => {
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
})
setTimeout(() => { console.log(6); }) console.log(7); // Result: 1475236Copy the code

Several concepts of JS Runtime

Call Stack Call stack

  • Definition: The call stack is the browser’s JavaScript interpreter trace
    Function execution flowA mechanism whereby a function call forms a stack of several frames. (The stack is characterized by lifO)
  • What it does: Through the call stack, we can track: which function is executing; Which function is called in the body of the executed function; And the context + scope for each frame
  • Mechanism:

    • Each time a function is called, it is added to the call stack and executed
    • If the calling function also calls other functions, add the new function to the call stack and execute it immediately
    • After execution, the interpreter clears the function off the stack and continues to execute the remaining code in the current execution environment
    • When the allocated call stack is full, the”
      Stack Overflow The Stack overflows“Error

Heap heap

Heaps a large (usually unstructured) area of memory in which objects are allocated

Task Queue Message queue

The JS runtime contains a message queue, and each message queue is associated with a callback function that processes the message. (The queue is characterized by first in, first out)

  1. When the call stack is empty, the Event loop will be the next message in the message queue
  2. The processed message is removed from the queue,
  3. The message is invoked as an argument to the callback function associated with it
  4. A new stack frame is added to the call stack for the function call
  5. The Event loop repeats steps 1-4 when the call stack is empty again

Typically, tasks in a Task queue are called macroTasks.

The following types of asynchronous API callbacks are macro tasks:

  • setTimeout
  • MessageChannel
  • postMessage
  • setImmediate

Single Thread Single Thread

  • Single thread = monotone stack = one thing at a time
  • Why can a single thread achieve asynchrony and concurrency?
  • Because single thread refers to the JS Runtime
  • Browsers and Node provide apis that allow you to call other threads to do concurrent asynchronous tasks, such as network requests, DOM, and setTimeout

Non – blocking non-blocking

  • Blocking: the browser is waiting for code that takes a long time (eg. Network requests,I/O) cannot process anything else, including user responses.
  • Solution to block: Asynchronous tasks
  • How are asynchronous tasks implemented? Depending on
    Asynchronous APIand
    Event Loop Indicates the event loop
  • One very interesting feature of JavaScript’s event loop model that distinguishes it from many other languages is that it
    Never blockSo while an application is waiting for an asynchronous task, it can still handle other things, such as user input. (A few for historical reasons
    exception, such as
    alertor
    Synchronous XHRBut they should be avoided as much as possible. There are exceptions to the rule
    [1](But it’s usually an implementation error rather than anything else).

Don’t be preempted

After each message has been executed in its entirety, other messages will be executed.

Advantages: When a function is executed, it is not preempted, and only after it is finished can other code be run to modify the data that the function operates on.

Disadvantages: When a message takes too long to process, the browser can’t handle user interaction,eg. Scrolling and clicking, this is also the cause of the “lag phenomenon” of poor web performance.

It is therefore a good practice to shorten the processing time of a single message and, where possible, to crop a message into multiple messages. To ensure smooth rendering of the browser’s 60 frames per second, i.e. processing time per message < 1000ms/60=16ms,

Event Loop Indicates the Event Loop

Event Loop is an execution model with different implementations in different places. Browsers and NodeJS implement their own Event loops based on different technologies.

  • The browser Event Loop model is in the HTML5 specification
    [2]The specific implementation is done by the browser vendor.
  • NodeJS Event Loop is implemented based on Libuv. You can refer to the official Node documentation
    [3]And libuv’s official documentation
    [4].

Browser EventLoop running mechanism (without microTask)

  • All synchronization tasks are there
    The main threadExecute on, form one
    call stackThe call stack
  • Can be achieved by
    The browser APIThe call runs in another thread
    Asynchronous tasks
  • Outside the main thread, there is a message to be processed
    Message queue Task Queue. Each message is associated with a callback function that processes the message.
  • When all synchronization tasks in the main thread call stack are completed, the system reads the Task Queue, takes the most advanced message as a parameter, and puts its associated callback function on the main thread call stack for execution

Add a message

  • In browsers, if an event has an event listener, when the event is triggered, a message is added to the message queue.
  • In addition to events, other apis provided by the browser, such as setTimeout, XHR, and other asynchronous tasks, add messages to the message queue after the task ends

setTimeout(fn,n)

  • setTimeoutThe second parameter n in is the minimum delay for a message to be enqueued
  • Therefore, rather than guarantee that the callback must execute within n milliseconds, the callback is guaranteed to be added to the message queue after n milliseconds, depending on the messages waiting to be processed in the message queue and the functions already in the call stack.
  • Zero delay:
    setTimeout 0Is used to queue the callback immediately, instead of executing it immediately in 0s

The debug a demo

// demo
function bar(){
    debugger
    console.log('bar')
    foo()
}
function foo(){
    debugger
    console.log('foo')
    setTimeout(function(){
        debugger
        console.log('setTimeout')}} (1000)function all(){
    debugger
    console.log('anounymous')
    bar()
})()
Copy the code

Schematic diagram

Extension of knowledge: webWorker & Cross Runtime Communication

  • each
    WebWorker**iframe, ** browser Windows have their own runtimes, i.e., each has its own Call stack, heap, queue.
  • Different runtimes can be accessed via postMessage
    [5]Method to communicate.

PostMessage:

// eg. When a window can get a reference to another window, for example, targetWindow = window.opener otherwindow.postMessage (message, targetOrigin, [Transfer]);Copy the code

OtherWindow: a reference to another window:

  • Iframe contentWindow
  • The window object returned by executing window.open
  • The child frame window object retrieved from window.frames

Message: Data to be sent to other Windows is serialized by a structured cloning algorithm [6]

TargetOrigin: Used to specify which Windows can receive message events

Transfer: a string of ‘Transferable’ [7] objects transmitted simultaneously with Message. Ownership of these objects is transferred to the receiver of the message, and ownership is no longer retained by the sender.

Structured cloning algorithm:

Used to clone complex objects

Cannot clone: Error, Symbol, Function object, DOM node

Cannot clone: descriptor of property, lastIndex field of RegExp object, property on prototype chain

Transferable object:

An abstract interface that represents objects that can be passed in different executable contexts. (Abstract: No properties and methods are defined)

Different execution contexts: for example, between the main thread and the webworker.

ArrayBuffer, MessagePort, and ImageBitmap are implemented in this interface.

Receiving messages:

window.addEventListener("message", receiveMessage, false);

functionReceiveMessage (event) {// event.data: sent object // event.origin: origin of the message sender window // event.source: reference to the message sending window}Copy the code

UI Rendering Task & Performance Optimization

Browser Rendering – Rendering Task steps

  • RequestAnimationFrame API(in Chrome, Firefox, WEB standard compliant)
  • Style calculation Calculates the style
  • Layout calculation
  • Paint actually renders pixel data
  • RequestAnimationFrame API(on Edge, Safari)

Render blocking

Specifically, if the CALL stack of the JS Runtime cannot be emptied, for example, if the Event loop adds a time-consuming callback to the Call stack, the main thread of the browser will be occupied and render related work will not be performed. User interaction events are also added to the message queue waiting for the call stack to empty and are not executed, so they cannot respond to user actions, causing a “stutter” phenomenon that blocks rendering.

60FPS

When event loop processes message queues, we advocate shortening the processing time of a single message, and try to cut a message into multiple messages when possible, rendering Task can perform between messages. The UI Rendering Task is usually executed 60 times per second, but in most browsers that follow W3C recommendations, the number of callbacks usually matches the number of browser screen refreshes. ) That is, each Event loop takes less than 16.67 milliseconds to process the message and execute the callback.

demo1:

Append an element and set display= None to hide it. Don’t worry about flashing, because these two lines will be executed in an Event loop. Only these two lines will complete and clear the current call stack. To execute the next UI Render Task

document.body.appendChild(el)
el.style.display='none'
Copy the code

demo2:

The following code, which repeatedly hides an element, looks expensive, but during RenderingTask, only takes the final result and renders it.

button.addEventListener ('click,()=>{
box style. display='none'; box style. display ='block'; box style. display ='none'; box style. display ='block'; box style. display='none'; box style. display ='block'; box style. display ='none'; box style. display ='block'; box style. display ='none';
})
Copy the code

requestAnimationFrame

  • RAF, for short, is a Web API that requires the browser to call a specified callback before the next redraw, usually for animation
  • RAF allows browsers to optimize the processing of concurrent animations in a single backflow and redraw, and RAF is executed before each UI refresh, resulting in higher frame rates for animations
  • RequestAnimationFrame () is either running in the background TAB or hidden
    <iframe>RequestAnimationFrame () is paused to improve performance and battery life

Demo1: An example of optimizing an animation with requestAnimationFrame

/ / use the RAFfunction callback(){ moveBoxForwardOnePixel(); requestAnimationFrame(callback) } callback(); / / usesetTimeout
function callback(){
 moveBoxForwardOnePixel();
 setTimeout(callback,0)
}
Copy the code

Effect:

Demo2: Use RAF to control the animation execution order. The requirement is the horizontal position change of box elements: 1000→500

button addEventListener ('click,()=>{ box.style.transform = 'translateX(1000px)' box.style.transition= 'transform 1s ease-in-out' box.style.transform = 'translateX(500px)'}) // Since the above code is executed together, // 1000px will be ignored when rendering, and the browser will take 500 as the final value for the next frame, // So the effect of the above code is: element displacement 0->500 // Switch to button addEventListener ('click,()=>{
 box.style.transform = 'translateX(1000px)'
 box.style.transition= 'transform 1s ease-in-out'
 
 requestAnimationFrame(()=>{
  box.style.transform = 'translateX(500px)'})}) // The initial rendering value of 1000 is valid, but during the next rendering task, RAF executes first, so 500 will cover 1000. The rendering effect is 0->500. Nested call RAF button addEventListener ('click,()=>{ box.style.transform = 'translateX(1000px)' requestAnimationFrame(()=>{ requestAnimationFrame(()=>{ box.style.transition= 'transform 1s ease-in-out' box.style.transform = 'translateX(500px)'})})})Copy the code

Visualization: Event loop and Rendering

Ideal state

The setTimeout waste

Effect of interval calls to setTimeout: Waste

Previous animation repository handling: setTimeout(animFrame, 1000/60)

But this kind of processing is unstable and can be inaccurate because

Stable and ordered state of RAF

MicroTask micro tasks

Microtasks, also called jobs.

Microtask asynchronous type

After the execution of some asynchronous tasks, their callbacks will enter the MicroTask Queue to be invoked later. These asynchronous tasks include:

  • Promise.then
  • MutationObserver
  • Process. nextTick (Node only)
  • Object.observe

⭐ Event Loop running mechanism (including MicroTask)

Execution order of tasks in event loop:

  1. Synchronizes code execution until the call stack is empty
  2. Microtask: After the call stack is cleared, it is preferentially executed
    allIf there is a new microtask, ** continues to execute the new microtask until the MicroTask queue is empty
  3. Task Queue: Executes the first task in the task queue. Subsequent tasks are not processed
  4. Repeat steps 2-3 each time the call stack is cleared

Two key points:

  • Microtasks block the browser: If new microtasks are continuously created during the execution of microtasks, the browser will block
  • The execution of microtasks will vary depending on the JS stack
    Whether the call stack is emptyTo determine whether the microtask will be performed.

An intuitive example:

Promise.resolve().then(()=>{
    console.log('microtask 1')
})
Promise.resolve().then(()=>{
    console.log('microtask 2')
})
console.log('sync code')
setTimeout(()=>{
    console.log('macro task 1')
    Promise.resolve().then(()=>{
        console.log('microtask 3')})}, 0)setTimeout(()=>{
    console.log('macro task 2'},0) // Result: // Macro task 1 has the priority to execute the call stack after the synchronization code is executed. If the microTask queue is empty, you can execute a macro task at the top of the queue. If the microTask queue is empty, you can execute a macro task at the top of the queue. Therefore, microtask //macro task is executed first and macro task is executed last to clear the task queueCopy the code

The flow chart

Demo1: The call stack is not cleared and microtask is not executed

Executing a piece of code in the console is treated as synchronous code. Listener1 executed, task queue + 1, but because be simultaneous execution of code, so it is immediately execute listener2, micro task queue + 1, so the order is listener1, listener2, microtask1, microtask2

Demo2: After the call stack is cleared, microTask takes precedence over Macro Task

Executing two setTimeouts synchronously adds Listener1 and Listener2 to the Task queue and completes the synchronization. The task queue will execute all microtasks first, even if the task queue still has Listener2. After all microtasks are cleared, the task queue will execute listener2. So the output sequence is listener1, microtask1 listener2, microtask2

Demo2 demo3:

User click event

Since the click event is added to the Task Queue, the result of demo3 is the same as the result of DEMO2

Demo4: not with

Js calls the click() event

Because click is executed manually in the code, both listeners are executed synchronously, so demo4 and DEMO1 have the same structure.

Demo5: Micro takes precedence over Macro

Demo6: comprehensive example

// Console. Log (1);setTimeout(() => { console.log(2); / / callback2,setResolve ().then(() => {console.log(3)// callback3, promise.then belongs to the microtask}); }); New Promise((resolve, reject) => {console.log(4)// Resolve (5)}). Then ((data) => {console.log(data); // callback5, promise. then belongs to the microtask})setTimeout(() => { console.log(6); / / callback6,setTimeout belongs to a macro task}) console.log(7); Queue: Task Queue: callback2, callback6 Microtask: Queue: Task Queue: callback6 Microtask: Callback6 Microtask: Queue: task Queue: callback6 Callback3 performs microtask Callback3 first, and callback6 is executed last when the call stack is clearedCopy the code

Demo7: comprehensive example

console.log('main start');

setTimeout(() => {
  //cb1
    console.log('1');
    Promise.resolve().then(() => {
   //cb2
   console.log('2')}); }, 0); Promise.resolve().then(() => { //cb3 console.log('3');
    Promise.resolve().then(() => {
   //cb4
   console.log('4')}); }); console.log('main end'); Queue: Task queue: cb1 MicroTask queue: cb1 Microtask queue: cb1 Cb3 performs the microtask cb3 first, and then the call stack is cleared. Queue: Task Queue: CB1 Microtask queue: CB4 Performs the microtask CB4 first, and then the call stack is cleared. Cb1 MicroTask Queue: empty Cb1 and cb2 are executedCopy the code

Rendering task execution order

In the event loop execution mechanism above, rendering task is not mentioned, because the browser decides when to execute the rendering task, which is related to the screen refresh rate of the current device and other factors. It can be determined as follows:

  • RAF is executed during the initial rendering task
  • If more than one RAF callback is defined, it will be added to
    Animation queueDuring UI Rendering, the Animation queue will be emptied. Different from MicroTask, if a new Animation task is added to the queue during the Animation queue cleaning, No new animation task will be processed during rendering task execution.

The difference between MacroTask, MicroTask and animation Task can be seen in the following GIF for horizontal comparison:

The resources

[1] There are exceptions to the exception:

https://stackoverflow.com/questions/2734025/is-javascript-guaranteed-to-be-single-threaded/2734311#2734311

[2] Specification of HTML5:

https://www.w3.org/TR/html5/webappapis.html#event-loops

[3] Official Documentation:

https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

[4] Official Documentation:

http://docs.libuv.org/en/v1.x/design.html

[5]postMessage:

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage

[6] Structured clone algorithm:

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm

[7]
Transferable:

https://developer.mozilla.org/zh-CN/docs/Web/API/Transferable

[8] HTML specification:

https://www.w3.org/TR/html5/webappapis.html#event-loops

[9]NodeJS Event Loop

https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick/#what-is-the-event-loop

[10] MDN

https://developer.mozilla.org/zh-CN/docs/Glossary/Call_stack

[11]Jake Archibald’s speech at JsConf. Asia [In The Loop]

[12] What the Heck is the Event Loop Anyway by Philip Roberts at JSConf

[13] Event Loop visual website by Philip Roberts:

http://latentflip.com/loupe/

[14]JS Runtime Runtime – MDN:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop