“This article has participated in the call for good writing activities, click to view: the back end, the big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!”

preface

Everyone knows that javascript is a single-threaded, non-blocking language. Single-threaded, non-blocking and asynchronous are all hallmarks of javascript and javascript itself. Before we begin today’s main event loop, a quick word about browser internals. We all know that the present mainstream browser is a multi-process structure, multi-process benefits is happened when a process problem, does not cause an die because of a process problem, browser, caching, multiple processes including user interface process, network process, plug-ins, GPU, process, and rendering process, the rendering process is responsible for the page will be presented to the user, There is a main thread in the process, and javascript runs in that main thread. In this main thread, not only do you run javascript code, but you also need to do things like render the interface.

Processes are the smallest unit of CPU resource allocation, while threads are the smallest unit of CPU scheduling

It’s necessary for javascript to be single-threaded, and why is that? Javascript was originally and primarily executed in the browser environment, and now also has the Node runtime environment, which requires all sorts of Dom manipulation. Given that javascript is multithreaded, two threads might operate on a Dom element at the same time, one adding an event to it and the other deleting the Dom element, which would also cause a lot of problems. In order not to have a situation like this, So javascript chooses to use only one main thread to execute the code, thus ensuring consistent program execution. So how does the javascript code execute? Let’s talk about the execution stack, where our javascript code is executed

Execution stack

Now take a look at how javascript executes code line by line. In the following code, there are three methods: add, add2, and printAdd2. Because today’s focus is on event loops, I’ve simplified some of the details of javascript execution, starting with the execution stack (also known as the call stack).

function add(a,b){
  return a + b;
}
function add2(x){
  return add(x,2);
}
function printAdd2(x){
  var result = add2(x);
  console.log(result);
}
printAdd2(3);
Copy the code

When a series of methods are called in sequence, because JS is single-threaded, only one method can be executed at a time, and these methods are placed in a first-in, last-out container called the execution stack. Javascript parsers read and parse the code from top to bottom. Each function corresponds to the space of the function private memory space. When printAdd2(3), it pushes printAdd2(x)

Then, because the add2 method is called in printAdd2, the add2 method is pushed on the stack, and so on, add is pushed on the stack. Then you execute these functions in sequence, counting the stack in order from add to printAdd2.

I’ll give you the specific execution sequence in graph form,

non-blocking

In fact, the so-called blocking, blocking is a phenomenon, but some code blocks are slow to execute, give you the impression that the browser is stuck, such as network request, image download, etc., implementation of such functions of the code will be blocked. Now we implement a thread sleep effect through the for loop.

function sleep(delay){
  for(var start = Date.now(); Date.now() - start <= delay;) {}; } sleep(1000)
console.log("complete");
Copy the code

The sleep function here mimics Java and Python’s ability to provide thread control, because js is a single line, so when we execute sleep(1000), the program doesn’t run down, For (var start = date.now (); Date.now() – start <= delay;) {} completes the loop before printing complete.

var btn = document.getElementById("btn");
btn.addEventListener("click".function(ev){
  console.log("click on me");
});

function sleep(delay){
  for(var start = Date.now(); Date.now() - start <= delay;) {}; } sleep(10000);
console.log("complete");
Copy the code

How does javascript solve the blocking problem? Because javascript has non-blocking tags, we’ll talk about event loops. There’s really nothing to explain in the above code. Add a BTN button to the browser page and add a click event to the BTN, which prints Click on me at the console. When it runs to ·sleep, the world stops because the sleep method blocks the thread.

Concurrency and event loops

Concurrency and multithreading often go hand in hand, and it seems like javaScript, which is a single thread, is a little weak. However, the event loop mechanism in javascript makes running javascript feel a bit concurrent.

var btn = document.getElementById("btn");
btn.addEventListener("click".function(ev){
  console.log("click on me");
});
setTimeout(function(){
  console.log("do something...")},1000);

console.log("complete");
Copy the code

Because of the event loop, we can happily and freely click on BTN and experience the non-blocking pleasure of the event loop.

Looking at this diagram, let’s look at the pieces that appear in the diagram, and then tie them together by explaining the code above to illustrate how javascript works asynchronously, or in other words, in parallel, based on event loops.

  • Web apis: When writing Web code in JavaScript, browsers provide a number of Web apis that JavaScript can call.
  • Execution stack: Also known as a “call stack” in other programming languages, it is a stack with LIFO (LIFO) data structures that are used to store all execution contexts created while the code is running.
  • Callback queue: Later also called task queue,
  • Event loop: In fact, the event loop will continue to circulate. Each loop can be regarded as a tick. Each event loop will do two things: observe whether the execution stack is empty; if it is empty, whether there is any task in the task queue; if there is any task, the task will be executed.

Javascript is an execution-to-completion process, so get the button element and assign it to the variable BTN, so that BTN has a reference to the DOM element, and then go down to BTN,addEventListener, because the event listener is, This is an asynchronous API, so it is handled by WebAPI. When the user clicks on a button and the click event is triggered, the Callback function is sent to the TaskQueue, represented by the Callback queue.

When the asynchronous AP IsetTimeout is encountered, the asynchronous callback function is handed over to the Web API for processing. (Here, the timer triggers the thread. After 1000ms, the onTimeout is pushed into the task queue after the triggering condition is met.

The main thread continues down to console.log, which prints complete in the console. There is now an event listener and timer in WebAPI.

When the timer times the specified time, the OnTimeout callback task is placed in the callback queue, waiting for an event loop to remove it from the callback queue and place it on the execution stack to execute the task

The main thread will then execute the OnTimeout task console.log output to the console

When the user clicks a button, onClickeEvent listens for the click event and puts the clicked callback task from WebAPI into the callback queue. The event loop first looks at the execution stack, and there is no function in the execution stack to execute, and then looks at the callback queue. Found a task OnClick waiting to be processed in callback queue

Since the execution stack has no code to execute, the event loop puts the Onclick task on the execution stack to execute, and the execution task executes the console.log code in the callback

Tasks and task queues (callback queues)

We can loop events, it’s a constant loop, to see if there’s a task in the call queue, so what is a task? A task is executing a piece of javascript code. So what’s a task queue? A queue is a new in, first out order. You can place click callback tasks, timing tasks, and network request tasks. Tasks are executed in order to complete, so there is no need to worry about synchronization and so on. The task queue works with the rendering process, and when we add a DOM element or change the STYLE of a DOM element, the rendering process is initiated to update the change to the screen.

The rendering process is executed immediately after the completion of the task execution in the task queue. And browsers are smart enough not to do more than they need to. Our screen refresh rate is 60 times a second. This is approximately every 16 milliseconds, which means that the render process will wait for time to execute even after the task queue is complete, so each interval may have some idle time between the completion of the task and the start of rendering.

However, if the task takes too long, the rendering process may be delayed for more than 16 milliseconds, and the image will look stuttering. We can split time-consuming tasks into multiple tasks and put them into the next task queue.

function veryLongTask(){
  firstPartTask()
  setTimeout(restPartTask)
}
Copy the code

The code used here is to split a time-consuming task into two tasks by setTimeout.

Thus, after a task is completed, networked pipes can be run. Once the task is done, the render pipeline can run, but the browser is smart, right? They don’t like to do work they don’t need to do, and there’s really no need to run the render pipeline unless the screen is about to refresh, so your screen refreshes on average 60 times per second, once every 16 milliseconds, so if we run a task, the render pipeline will wait those 16 milliseconds before running.

while(true){
  queue = getNextQueue();
  task = queue.pop();
  execute(task);
  
  while(microtaskQueue.hasTasks())
    doMicrotask();
  if(isRepaintTime()) repaint();
}
Copy the code

Requestanimationframes, macro tasks, and microtasks will also be covered later.