How to enter the browser event loop by asking:

console.log('script start')
setTimeout(function () {
  console.log('setTimeout')},0);
Promise.resolve().then(function () {
  console.log('promise1')
}).then(function () {
  console.log('promise2')})console.log('script end')
Copy the code

Try it out, write it out by hand, and then, after reading this article, run the code again to see if it works as expected

Single thread

define

Single threading means that all tasks need to be queued until the first task finishes before the next task can be executed. If the first task takes a long time, the next task has to wait forever.

why

The single thread of javascript, relative to its purpose. As a browser scripting language, javascript’s primary purpose is to interact with users and manipulate the DOM. This means that it has to be single-threaded, which can cause complex synchronization problems. Javascript, for example, assume that have two threads at the same time, a DOM node is added, the other is to remove the DOM node, the browser should should which one shall prevail, if an increase in a single thread to manage multiple threads, although has solved the problem, but increased the complexity, why not use single thread, performing a sequence, Only a single event is executed at a time. In order to make use of the computing power of multi-core CPU, HTML5 puts forward the Web Worker standard, which runs javascript to create multiple threads, but the sub-threads are completely controlled by the main thread and cannot operate DOM. So, this standard doesn’t change the single-threaded nature of javascript

In the browserEvent Loop

The event loop gets its name from how it is often implemented:

while(queue.waitForMessage()) {
    queue.processNextMessage();
}
Copy the code

The advantage of this model is that it must process one message (Run to completion) before processing the next, making the application more traceable. Unlike C, which can switch from one thread to another at any time. The downside, however, is that the user interaction can be affected if the synchronous code blocks

macroTaskandmicroTask

Macrotasks are also called tasks. Callbacks containing synchronous tasks and some asynchronous tasks are in turn added to the Macro Task Queue. Macrotasks include:

  • The script code block
  • setTimeout
  • requestAnimationFrame
  • I/O
  • UI rendering

Microqueues, microtasks, also known as Jobs. Other asynchronous tasks whose callbacks go to the Micro Task Queue to be called later include:

  • Promise.then
  • MutationObserver

Here is a diagram of the Event Loop

javascript

  1. So we’re going to execute the macro queue and we’re going to fetch the first segmentscriptThat’s equivalent to onemacrotask, so he will perform synchronization code first, when encountering for examplesetTimeoutThe asynchronous task is pushed to the end of the macro queue.
  2. The currentmacrotaskWhen the execution is complete, the asynchronous task in the header is pulled from the microqueue for execution, and the length of the task in the microqueue is reduced by one.
  3. It then continues to pull tasks from the microqueue until there are no tasks in the entire queue. If, during the execution of the microqueue task, it generates againmicrotaskIs added to the end of the queue and is executed during the current cycle
  4. When the task in the microqueue is empty, the next task needs to be executedmacrotask, then execute the microqueue, and so on.

    And that’s what it boils down totaskIn the queue in ordertaskExecute, each time you executetaskWill checkmicrotaskWhether or not to be empty, not to be empty to execute all in the queuemicrotask. And then take the next onetaskIn this cycle

Call stack and task queue

A call stack is a stack structure in which a function call forms a stack frame. Stack frame: Each entity in the call stack is called stack frame, which contains the context information such as the parameters and local variables of the currently executing function. After the function is executed, its execution context will pop out from the stack. Here is the relationship between the call stack and the task queue:

debugger
chrome
call stack

call stack
macroTasks
microTasks

  1. First, three queues are empty until the code executes:
callStack: []
macroTasks: [main]
microTasks: []
Copy the code

As mentioned earlier, the entire code block is equivalent to a macroTask, so first press main() into the callStack, which is equivalent to the entire code block 2. Execute main to print the result of the synchronization code:

callStack: [main]
macroTasks: []
microTasks: []
Copy the code

3 is pushed into macroTasks and microTasks when setTimeout and promise are encountered. The three queues are:

callStack: [main]
macroTasks: [setTimeout]
microTasks: [promise]
Copy the code

When this code is finished executing, it prints:

script start
script end
Copy the code
  1. whenmainWhen the execution is complete, themicroTasksThe task incallStack, the three queues are:
callStack: [promise]
macroTasks: [setTimeout]
microTask: []
Copy the code

This promise will be printed when it completes execution

promise1
Copy the code

If there is a microtask, add it to the end of the microqueue and execute it in the current tick. When the task is removed from macroTasks, the state of the three queues is as follows:

callStack: [setTimeout]
macroTasks: []
microTask: []
Copy the code

The final output is setTimeout. An event loop is an event loop that pulls events from two queues and executes them. After the above example, it is not easy to understand

Event Loopuse

This section requires some knowledge of the Vue source code, if not, you can skip it.

NextTick principle

Vue implements the nextTick function, passing in a cb function, which is stored in a queue and triggers all cb events in the queue in the nextTick. We define an array callbacks to store tasks that need to be executed for the next tick. Pending is a flag bit that is guaranteed to execute only once before the next tick. TimeFunc is a function pointer that uses a different method depending on browser support

function nextTick() {
  const callbacks = [];
  let pending = false;
  let timeFunc
}
function nextTickHandler() {
  pending = false;
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
Copy the code

The nextTickHandler simply calls all the functions stored by the Callbacks. TimeFunc implementation

if (typeof Promise! = ='undefined') {
  timeFunc = (a)= > {
    Promise.resolve()
      .then(nextTickHandler)
  }
} else if (typeofMutationObserver ! = ='undefined') {
  // ...
} else {
  timeFunc = (a)= > {
    setTimeout(nextTickHandler, 0)}}Copy the code

Promise and MutationObserver are preferred because their callbacks are executed earlier in MicroTask than setTimeout. Here is an implementation of MutationObserver:

const counter = 1;
const observer = new MutationObserver(nextTickHandler)
const textNode = document.createTextNode(counter)
observer.observe(textNode, {
    characterData: true,
})
timeFunc = (a)= > {
    couter = (counter + 1) % 2;
    textNode.data = String(counter)
}
Copy the code

Each time timeFunc is called, the value of counter is changed, and the DOM value is changed, triggering the Observer to implement the callback. SetTimeout is used in environments where neither method is supported. SetTimeout is executed on the next tick. In this way, according to THE HTML Standard, after each task is finished, the UI will be rendered again. Then, after the current task is finished, the latest UI will be available. Otherwise, it needs to wait for the next tick to update the data, but it has already been rendered twice

Vue batch asynchronous update policy

Note: This section requires some knowledge of the Vue source code. Here is an example where clicking the button increases count from 0 to 1000. If every change in count triggers an update to the DOM, then the DOM updates 1000 times, and the phone freezes.

<div>{{count}}</div>
<button @click="addCount">click</button>
Copy the code
data () {
    return {
        count: 0,}},methods: {
    addCount() {
        for (let i = 0; i < 1000; i++ ){
            this.count += 1; }}}Copy the code

So how does Vue avoid this? Every time a setter method for data is triggered, the corresponding Watcher object is pushed into a queue, and the Watcher object is used to trigger updates to the real DOM.

let id = 0;
class Watcher {
    constructor() {
        this.id = id++;
    }
    update() {
        console.log('the update: + id);
        queueWatcher(this);
    }
    run() {
        console.log('run:+ id); }}Copy the code

When the setter is fired, the Update of the Watcher object is triggered, and the run method is used to update the page.

queue
watcher
watcher
id
watcher
waiting
tick
flushSchedulerQueue
queue
watcher
run

const has = {};
const queue = [];
let waiting = false;
function queueWatcher(watcher) {
    const id = watcher.id;
    if (has[id] == null) {
        queue.push(watcher)
        has[id] = true;
    }
    if(! waiting) { waiting =true;
        nextTick(flushScheulerQueue)
    }
}
function flushScheulerQueue() {
    for (index = 0; index < queue.length; index++) {
        watcher = queue[index]
        id = watcher.id;
        has[id] = null;
        watcher.run();
    }
    wating = false;
}
Copy the code

So when a value changes multiple times, you actually only add one value to the queue, and then you do a callback in nextTick, and you update the page through the queue, so you only update the DOM once when you change data multiple times, but you also want to avoid this multiple changes in your project. For example:

const watcher1 = new Watcher();
const wather2 = new Watcher();

watcher1.update();
watcher2.update();
watcher2.update();
Copy the code

A watcher fires two updates, but the output is as follows:

update: 1
update: 2
update: 2
run: 1
run: 2
Copy the code

Although watcher2 triggers two updates, there is only one Watcher in the queue because Vue filters the same Watcher. The call to the run method is called in nextTick, which is the microTask mentioned earlier. The result above is printed

This article describes the JS event polling mechanism, is not more clear to the synchronous asynchronous understanding. Learn a knowledge point is the most important to its landing, you can try more, more in-depth understanding of the event polling mechanism. Github asks for your attention, thanks.