This article belongs to the original article, reprint please note — fromTao Yuan xiao Pan’s blog

Wow. Wow. Ji ji

Many conceptual principles can be summed up in a couple of sentences, but a lot of detail is lost. The actual code, on the other hand, can’t ignore the details, but is simplified at best.

So let’s simulate the event loop mechanism with pseudocode.

Talk is cheap. Show me the code.

Easier said than done, ma Su in history may be the best representative of the negative.

Why an event loop and not something else?

The main js thread does various types of tasks, such as dom events, layout calculations, JS tasks, user input, animation, and timers.

How to solve new tasks in the future?

Events cannot be executed at the same time, and new events will be generated in the future. Therefore, it is necessary to have a mechanism like a receptionist, who always stays there to check whether there is a new task and executes it as soon as there is a new task. This is the event loop mechanism.

while(true) {
  doSomething()
}
Copy the code

How to solve the accumulation of new tasks?

There are so many new tasks that the receptionist can’t handle multiple tasks at the same time and has to queue people up, which is the task queuing mechanism.

Why can’t you multitask? This is because JS (the main thread of the renderer) is executed in single-thread mode.

A queue is a first-in, first-out data structure that can be understood as an array in JS.

const queue = []
const stop = false

while(true) {
  const task = queue.unshift()
  task()

  // Exit flag
  if (stop) {
    break}}Copy the code

A high-priority task is blocked

If there is only one message queue, then high-priority tasks are always waiting, which can result in page stalling. So there are several queues based on the type of task. The priorities are in descending order.

  • The user interaction
  • Synthesis of page
  • Default (resource load, timer, etc.)
  • Idle (garbage collection, etc.)
class Queue {
  handleQueue = []  // The interaction queue
  composeQueue = [] // compose queue
  baseQueue = []    // The default queue
  freeQueue = []    // The queue is idle

  // Insert a new task
  add(task, type) {
    if (type === 'handle') {
      this.handleQueue.push(task)
    } else if (type === 'compose') {
      this.composeQueue.push(task)
    } else if (type === 'base') {
      this.baseQueue.push(task)
    } else if (type === 'free') {
      this.freeQueue.push(task)
    }
  }

  // Get a task
  get() {
    const queue = []
    if (handleQueue.length > 0) {
      queue = handleQueue
    } else if (composeQueue.length > 0) {
      queue = composeQueue
    } else if (baseQueue.length > 0) {
      queue = baseQueue
    } else if (freeQueue.length > 0) {
      queue = freeQueue
    }
    return queue.unshift()
  }
}

const queue = new Queue()
const stop = false

while(true) {
  const task = queue.get()
  task()

  // Exit flag
  if (stop) {
    break}}Copy the code

At different stages of the page, the high quality goal is different

During the page loading phase, the first goal is to render the page first. In the interactive stage, the first goal of a page is to respond to user actions in a timely manner.

In order to meet the objectives of different phases, the priorities of task queues in different phases need to be adjusted.

class Queue {
  handleQueue = []
  composeQueue = []
  baseQueue = []
  freeQueue = []
  priority = []
  // Set the priority
  setPriority(lifecycle) {
    if (lifecycle === 'pageload') { // The page loads
      this.priority = ['baseQueue'.'handleQueue'.'composeQueue'.'freeQueue']}else if (lifecycle === 'handle') { // The interaction phase
      this.priority = ['handleQueue'.'composeQueue'.'baseQueue'.'freeQueue']}else if (lifecycle === 'free') { // Idle phase
      this.priority = ['baseQueue'.'handleQueue'.'freeQueue'.'composeQueue']}}get() {
    const curr = []
    // Get tasks in priority order
    this.priority.forEach(priority= > {
      const queue = this[priority]
      if (queue.length > 0) {
        return queue.unshift()
      }
    })
  }
  / / to omit
  add(task, type){}}const queue = new Queue()
const stop = false

queue.setPriority('pageload')

while(true) {
  const task = queue.get()
  task()

  // Exit flag
  if (stop) {
    break}}Copy the code

How to do some tasks before rendering?

Sometimes we want to do a few more tasks before the current one is done, but if we get to the end of the queue, it may take too long or too short, and it may not be consistent.

So we added microtask queues, which allow you to do something when the current task is about to complete without waiting too long.

class Task {
  microQueue = []
  // Execute the task
  do() {
    // start doSomething
    // doSomething
    // end doSomething
    // Check the microtask queue
    if (microQueue.length > 0) {
      microQueue.forEach(microTask= > microTask())
    }
  }
  // Add microtasks
  addMicro(microTask) {
    this.microQueue(microTask)
  }
}

// omit, ibid
class Queue {
  add(task, type) {}

  get() {}

  setPriority(lifecycle){}}const queue = new Queue()
queue.add(new Task(), 'base')

while(true) {
  const task = queue.get()
  task.do()

  // Exit flag
  if (stop) {
    break}}Copy the code

Low level mission starvation phenomenon

If you have been performing high quality tasks, low level tasks will starve to death. Therefore, after executing a certain number of high quality tasks, you need to perform a low level task.

An asynchronous callback

Here is a common sense, although JS is a single thread execution, but the browser is multi-process.

An asynchronous task, which may be executed by another process or thread in the browser, is then notified by IPC to the renderer, which then adds the corresponding message to the message queue.

How is the setTimeout implementation mechanism different?

Because of the concept of time, it cannot be put directly into the message queue. The browser has added a delay queue, where other delayed tasks are executed. The delay queue is checked each time a task in the message queue completes.

const delayQueue = []

// Check whether the task in the delay queue is running out of time
function checkDelayQueue () {
  delayQueue.map(task= > {
    if ('到期了') {
      task()
    }
  })
}

/ / to omit
class Queue {}

const queue = new Queue()

while(true) {
  const task = queue.get()
  task.do()

  checkDelayQueue()

  // Exit flag
  if (stop) {
    break}}Copy the code

At the end

The above code is not an actual browser implementation, but is intended to help you better understand the event loop mechanism.

I hope you write your own implementation.

reference

  1. “Browser Working Principles and Practices”
  2. JavaScript Ninja Secrets
Copy the code