React internally implements the requestIdleCallback method to optimize rendering performance. This article mainly introduces the implementation principle of requestIdleCallback in DOM environment. The idea is the same in non-DOM environment, but the API is different. Hope to help you better learn requestIdleCallback.

Look at a simple use of requestIdleCallback:

requestIdleCallback(myWork)
// A task queue
let tasks = [
  function t1(){
    console.log('Execute Task 1')},function t2() {
    console.log('Task 2')}]// Deadline is an object returned by requestIdleCallback
function myWork(deadline) {
  console.log('Remaining time of current frame:${deadline.timeRemaining()}`)
  The timeRemaining method returns the remaining time of the current frame
  if(deadline.timeRemaining() > 0 && tasks.length) {
    // We can do something here
    const task = tasks.shift()
    task()
  }
  // If there are still tasks that have not been executed, it is put into the next frame schedule to continue execution, similar to recursion
  if(tasks.length) {
    requestIdleCallback(myWork)
  }
}
Copy the code

RequestIdleCallback: requestIdleCallback: requestIdleCallback: requestIdleCallback

The most critical thing is that the timeRemaining method in deadline returns the remaining time of the current frame. How do we know the remaining time of the current frame (16.6ms) after the completion of relevant tasks? Then think about how to calculate the remaining time. (Of course, there may be no time left.)

RequestAnimationFrame helps us calculate the remaining time of the current frame.
  • The requestAnimationFrame callback is up to the system to decide when to call it, and is called before each drawing (note that this is before drawing; subsequent requestIdleCallback implementations need to calculate the end time of the current frame here).
  • In general, the drawing frequency of the system is 60Hz, so the callback is 1000/60=16.66ms once. In this way, it is guaranteed that the callback is executed every 16.66ms, so that there will not be the problem of frame loss resulting in lag.
requestAnimationFrame(function(rafTime){
    console.log('rafTime', rafTime)
})
Copy the code

RequestAnimationFrame accepts a callback and returns a parameter to the callback. Take a look at the MDN explanation for this callback:

Callback updates the function called by the animation frame before the next redraw (the above callback function). The callback function is passed the DOMHighResTimeStamp parameter, which has the same return value as performing.now () and represents the time when requestAnimationFrame() started executing the callback function.Copy the code

This explanation does not seem to make it clear how the rafTime parameter in the callback we wrote is calculated, i.e. DOMHighResTimeStamp or performance.now(), In order to facilitate the subsequent use of performance. Now ().

Take a look at how MDN interprets DOMHighResTimeStamp.

To make it easier to understand performance.now(), we can simply say that performing.now () is timed from 0 when the page opens up to the current time. A value that accumulates as the time spent on the current page increases, acting like a timer.

Ok, now we have the usage and parameters for requestAnimationFrame. The deadline. TimeRemaining () is used to calculate the remaining time of the current frame.

1. Current frame end time:We know that the callback to requestAnimationFrame is executed before the current frame starts drawing. That is, rafTime is the time when the current frame starts, if the execution time per frame is 16.66ms. FrameDeadline = rafTime + 16.66

2. Remaining time of current frame:Current Frame Remaining time = Current Frame End Time (frameDeadline) – Time spent on the current frame. The key is how do we know ‘time spent on current frame’, how do we calculate this, and this involves the knowledge of JS event loops. React is implemented using MessageChannel.

Code implementation, which notes also explains some specific implementation ideas:
let frameDeadline // The end time of the current frame
let penddingCallback // requestIdleCallback callback method
let channel = new MessageChannel()

// When this method is used, the requestAnimationFrame callback has been completed, and the remaining time of the current frame can be calculated by calling timeRemaining().
// Because MessageChannel is a macro task, it will wait for the main thread task to complete. We can understand that the requestAnimationFrame callback is executed in the main thread, and the onMessage method is executed only after the callback is executed.
SetTimeout is not executed until the main thread has finished executing.
channel.port2.onmessage = function() {
  // Determine whether the current frame ends
  // timeRemaining() calculates the timeRemaining in the current frame. If the time is greater than 0, the current frame has timeRemaining
  let timeRema = timeRemaining()
	if(timeRema > 0) {// Execute the callback and pass the parameters to the callback
		penddingCallback && penddingCallback({
      		// Whether the current frame is complete
      		didTimeout: timeRema < 0.// How to calculate the remaining time
			timeRemaining
		})
	}
}
// Calculates the remaining time of the current frame
function timeRemaining() {
    // Current frame end time - current time
	// If the result is > 0, the current frame has time left
	return frameDeadline - performance.now()
}
window.requestIdleCallback = function(callback) {
	requestAnimationFrame(rafTime= > {
      // Calculate the end time of the current frame
      frameDeadline = rafTime + 16.66
      // Store callback
      penddingCallback = callback
      MessageChannel is a macro task, meaning that the onMessage method will not execute until the current frame is completed
      // We can calculate the remaining time of the current frame
      channel.port1.postMessage('haha') // Send the content arbitrarily written})}Copy the code
Conclusion: Now that the simple version of requestIdleCallback has been implemented, let’s briefly conclude.
  1. FrameDeadline = rafTime + 16.66; frameDeadline = rafTime + 16.66;
  2. Based on the nature of the MessageChannel macro task, it is possible to calculate how long the current frame has been executed, and how long the current frame has been executed = frameDeadline-performance.now ().

This article mainly explains the simple implementation of requestIdleCallback in DOM environment, mainly explains the implementation ideas, hope to help you better understand the requestIdleCallback, there are a lot of changes in the code, if there is any mistake, please comment.