This article is an original article, please indicate if you reprint it-from Taoyuan Xiaopan’s blog

Wow wow

Many conceptual principles can be summarized in a few sentences, but a lot of details are inevitably lost. As for the actual code, the details cannot be ignored, at best it is simplified.

So let us use pseudo code to simulate the event loop mechanism.

Talk is cheap. Show me the code.

Easier said than done, Ma Di may be the best negative representative in history.

Why is the event loop mechanism, and not another mechanism?

The js main 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?

Various events cannot be executed at the same time, and new events will be generated in the future, so there needs to be a mechanism like the receptionist to stay there, always check whether there is a new task, and execute it whenever there is a new task. , This is the event loop mechanism.

while(true) {
  doSomething()
}

How to solve the accumulated new tasks?

There are too many new tasks. The front desk receptionist cannot handle multiple tasks at the same time, so everyone can only queue up. This is the task queue mechanism.

Why can't I handle multiple tasks at the same time? Because js (the main thread of the rendering process) is a single-threaded execution mode.

The queue is a first-in first-out data structure, which can be understood as an array in js.

const queue = []
const stop = false

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

  // 退出标志
  if (stop) {
    break
  }
}

High priority tasks are blocked

If there is only one message queue, then high-priority tasks have been waiting, which may cause page jams.
So according to the type of task, there are several queues. The priority goes down in order.

  • User interaction
  • Composite page
  • Default (resource loading, timer, etc.)
  • Idle (garbage collection, etc.)
class Queue {
  handleQueue = []  // 交互队列
  composeQueue = [] // 合成队列
  baseQueue = []    // 默认队列
  freeQueue = []    // 空闲队列

  // 插入新任务
  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() {
    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()

  // 退出标志
  if (stop) {
    break
  }
}

The page has different high-quality goals at different stages

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

In order to meet the goals of different stages, the priority of task queues in different stages needs to be adjusted.

natapp1

class Queue {
  handleQueue = []
  composeQueue = []
  baseQueue = []
  freeQueue = []
  priority = []
  // 设置优先级
  setPriority(lifecycle) {
    if (lifecycle === 'pageload') { // 页面加载
      this.priority = ['baseQueue', 'handleQueue', 'composeQueue', 'freeQueue']
    }
    else if (lifecycle === 'handle') { // 交互阶段
      this.priority = ['handleQueue', 'composeQueue', 'baseQueue', 'freeQueue']
    } else if (lifecycle === 'free') { // 空闲阶段
      this.priority = ['baseQueue', 'handleQueue', 'freeQueue', 'composeQueue']
    }
  }

  get() {
    const curr = []
    // 根据优先级顺序来获取任务
    this.priority.forEach(priority => {
      const queue = this[priority]
      if (queue.length > 0) {
        return queue.unshift()
      }
    })
  }
  // 省略
  add(task, type) {}
}

const queue = new Queue()
const stop = false

queue.setPriority('pageload')

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

  // 退出标志
  if (stop) {
    break
  }
}

How to do some tasks before rendering?

Sometimes we want to do some tasks immediately before the current task is completed, but if it is inserted at the end of the team, the time required may be long or short, which cannot be done stably as expected.

Therefore, the micro task queue is added. When the current task is about to be completed, some things will be executed without waiting too long.

class Task {
  microQueue = []
  // 执行任务
  do() {
    // start doSomething
    // doSomething
    // end doSomething
    // 检查微任务队列
    if (microQueue.length > 0) {
      microQueue.forEach(microTask => microTask())
    }
  }
  // 添加微任务
  addMicro(microTask) {
    this.microQueue(microTask)
  }
}

// 省略,同上
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()

  // 退出标志
  if (stop) {
    break
  }
}

Starvation in low-level tasks

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

Asynchronous callback

Let me talk about a common sense first. Although js is executed in a single thread, the browser is multi-process.

An asynchronous task may be executed by other processes or threads of the browser, and then the execution result is notified to the rendering process by IPC, and then the rendering process adds the corresponding message to the message queue.

What is the difference in the implementation mechanism of setTimeout?

Due to the concept of time, it cannot be directly put into the message queue. The browser has added a delay queue, and other delayed tasks are executed here. Every time a task in the message queue is executed, it is necessary to check the delay queue.

const delayQueue = []

// 检查延迟队列中的任务,是否到时间了
function checkDelayQueue () {
  delayQueue.map(task => {
    if ('到期了') {
      task()
    }
  })
}

// 省略
class Queue {}

const queue = new Queue()

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

  checkDelayQueue()

  // 退出标志
  if (stop) {
    break
  }
}

end

The above code is not an actual browser implementation, it is just to help understand the event loop mechanism better.

Hope you also write your own version.

refer to

  1. "Browser Working Principle and Practice"
  2. "JavaScript Ninja Cheats"

桃源小盼聊技术
733 声望16 粉丝

努力做到优秀。


引用和评论

1 篇内容引用
0 条评论