React Fiber源码逐个击破系列-scheduler

Scheduler是React中相对独立的模块,用于实现相似浏览器requestIdleCallback的功能。也是React16实现time slicing最核心的一个模块。node

在我阅读React源码的过程来看,scheduler就是整个React源码的缩影,例如链表的操做、全局变量、临时变量的控制等等。若是能先弄懂,对其余部分的代码阅读将会有极大的帮助。 我写这篇文章但愿将scheduler模块从React源码中抽离出来解读,不须要任何其余部分的知识,将这个模块做为阅读源码的突破口,在此我须要一些预备知识: 咱们先看一下整个scheduler的方法列表:react

image.png

  1. scheduler在React中的调用入口是unstable_scheduleCallback,咱们接下来说解都会从unstable_scheduleCallback这个函数开始。其中传入的参数callback是performAsyncWork,咱们这边先无论,直接简单的理解为是React要执行的一个任务,由于整个scheduler模块就是控制这个任务的运行时间。
  2. 关于expirationTime,expirationTime的意义是当前时间+任务预计完成时间,而每一个任务都有本身的expirationTime,在scheduler模块中,咱们能够理解为每一个任务的expirationTime小,优先级越高(由于越快过时)。 很简单的,判断一个任务是否超时,只要expirationTime < currentTime便可。

在目前React最新的代码中expirationTime中,expirationTime越小反而优先级越高,主要是为了减小sync判断,可是这个逻辑尚未修改到scheduler模块中,关于expirationTime还有不少内容,可是这里并非咱们的重点,这里先不讲。git

  1. 整个scheduler模块都在维护一个callback的环形链表,链表的头部是firstCallbackNode,当咱们遇到一个判断firstCallbackNode === null,咱们应该明白这是这判断这个链表是否为空。在后文中,这个链表的元素我称之为callbackNode, 链表称为callback链表
unstable_scheduleCallback

接下面咱们从入口函数unstable_scheduleCallback开始看, unstable_scheduleCallback函数的内容概述就是生成callbackNode,并插入到callback链表之中github

function unstable_scheduleCallback(callback, deprecated_options) {
  // callback是performAsyncWork,也就是咱们上文所说的任务
  // getCurrentTime就是获取当前时间
  // currentEventStartTime在react-dom中不会用到,先忽略
  var startTime =
    currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime();

  var expirationTime;
  if (
    // 这个判断与scheduler核心无关,先忽略
    typeof deprecated_options === 'object' &&
    deprecated_options !== null &&
    typeof deprecated_options.timeout === 'number'
  ) {
    // 从requestWork调用到这里,目前只会走这个分支
    // 目前来看timeout越小,优先级越大
    expirationTime = startTime + deprecated_options.timeout;
  } else {
    // 这一部份内容我在React源码中并无看到用的地方
    // 应该是还未完成的一部分代码
    // 这里先删除以避免影响阅读
  }
  // 这个就是callback环形链表的元素结构
  var newNode = {
    callback, // 须要执行的任务
    priorityLevel: currentPriorityLevel, // 这个值暂时用不到,先不看
    expirationTime, // 过时时间,上文已经介绍过
    next: null, // 链表结构next
    previous: null, // 链表结构previous
  };

  // 接下来部分就是将newNode插入到链表中,而且按expirationTime从大到小的顺序
  // firstCallbackNode 是一个双向循环链表的头部,这个链表在此模块(scheduler)模块维护
  // firstCallbackNode === null 说明链表为空
  if (firstCallbackNode === null) {
    // 给环形链表添加第一个元素
    firstCallbackNode = newNode.next = newNode.previous = newNode;
    ensureHostCallbackIsScheduled();
  } else {
    var next = null;
    var node = firstCallbackNode;
    // 从头部(firstCallbackNode)开始遍历链表,知道
    do {
    // 这个判断用于寻找expirationTime最大的任务并赋值给next
    // 也就是优先级最低的任务
     if (node.expirationTime > expirationTime) {
        next = node; // next这个局部变量就是为了从链表中找出比当前新进入的callback优先级更小的任务
        break;
      }
      node = node.next;
    } while (node !== firstCallbackNode); // 因为是环形链表,这是已经遍历一圈的标记

    // 这里环形链表的排序是这样的
    /* * head * next7 next1 * next6 next2 * next5 next3 * next4 * * 其中head的expirationTime最小,next7最大,其他的next的expirationTime从小到大排序, * 当next === null,走分支1,newNode的expirationTime是最大的(链表每一个element都小于newNode),因此须要将newNode插入head以前 * 当next === firstCallbackNode,newNode的expirationTime是最小的,也就是newNode要插入head以前,成为新的head, * 因此分支2须要修改链表的head指针 * */
    if (next === null) {
      // 分支1
      // No callback with a later expiration was found, which means the new
      // callback has the latest expiration in the list.
      next = firstCallbackNode;
    } else if (next === firstCallbackNode) {
      // 分支2
      // 这个分支是指新的callback的expirationTime最小,那么应该放在头部,这里直接改变头部(firstCallbackNode)指向newNode
      // 后面插入操做正常执行,与上面的判断分支相似
      // The new callback has the earliest expiration in the entire list.
      firstCallbackNode = newNode;
      ensureHostCallbackIsScheduled();
    }
    // 环形双向链表插入的常规操做,这里是指在next节点以前插入newNode
    var previous = next.previous;
    previous.next = next.previous = newNode;
    newNode.next = next;
    newNode.previous = previous;
  }

  return newNode;
}
复制代码

至此咱们明白了,这个函数的功能就是按照expirationTime从小到大排列callback链表。只要插入和排序一完成,咱们就会调用ensureHostCallbackIsScheduled浏览器

ensureHostCallbackIsScheduled
function ensureHostCallbackIsScheduled() {
  // 当某个callback已经被调用
  if (isExecutingCallback) {
    // Don't schedule work yet; wait until the next time we yield.
    return;
  }
  // Schedule the host callback using the earliest expiration in the list.
  var expirationTime = firstCallbackNode.expirationTime;
  if (!isHostCallbackScheduled) {
    isHostCallbackScheduled = true;
  } else {
    // Cancel the existing host callback.
    cancelHostCallback();
  }
  requestHostCallback(flushWork, expirationTime);
}
复制代码

ensureHostCallbackIsScheduled在后面会在各类状况再次调用,这里咱们只要知道,ensureHostCallbackIsScheduled,而且调用了requestHostCallback(flushWork, expirationTime)就能够了。bash

requestHostCallback
requestHostCallback = function(callback, absoluteTimeout) {
    // scheduledHostCallback就是flushWork
    scheduledHostCallback = callback;
    // timeoutTime就是callback链表的头部的expirationTime
    timeoutTime = absoluteTimeout;
    // rAF是requestAnimationFrame的缩写
    // isFlushingHostCallback这个判断是一个Eagerly操做,若是有新的任务进来,
    // 尽可能让其直接执行,防止浏览器在下一帧才执行这个callback
    // 这个判断其实不是很好理解,建议熟悉模块以后再回来看,并不影响scheduler核心逻辑
    // 有兴趣能够阅读https://github.com/facebook/react/pull/13785
    if (isFlushingHostCallback || absoluteTimeout < 0) {
      // absoluteTimeout < 0说明任务超时了,马上执行,不要等下一帧
      // Don't wait for the next frame. Continue working ASAP, in a new event.
      // port就是port1
      port.postMessage(undefined);
    // isAnimationFrameScheduled是指animationTick函数是否在运行中
    // 第一次调用必定会走进这个分支
    } else if (!isAnimationFrameScheduled) {
      // If rAF didn't already schedule one, we need to schedule a frame.
      // TODO: If this rAF doesn't materialize because the browser throttles, we
      // might want to still have setTimeout trigger rIC as a backup to ensure
      // that we keep performing work.
      isAnimationFrameScheduled = true;
      requestAnimationFrameWithTimeout(animationTick);
    }
  };
复制代码

咱们注意到函数名也有callback,可是这里是hostCallback,整个模块中的hostCallback函数都是指flushWork,咱们后面再讲这个flushWork。 注释中有一个疑点就是判断isAnimationFrameScheduled,这里是由于整个scheduler模块都是在animationTick函数中一帧一帧的调用的,咱们在下一个animationTick函数中会详细讲解。并发

var requestAnimationFrameWithTimeout = function(callback) {
  // callback就是animationTick方法
  // schedule rAF and also a setTimeout
  // localRequestAnimationFrame至关于window.requestAnimationFrame
  // 接下来两个调用时超时并发处理
  // 1. 调用requestAnimationFrame
  rAFID = localRequestAnimationFrame(function(timestamp) {
    // cancel the setTimeout
    localClearTimeout(rAFTimeoutID);
    callback(timestamp);
  });
  // 2. 调用setTimeout,时间为ANIMATION_FRAME_TIMEOUT(100),超时则取消rAF,改成直接调用
  rAFTimeoutID = localSetTimeout(function() {
    // cancel the requestAnimationFrame
    localCancelAnimationFrame(rAFID);
    callback(getCurrentTime());
  }, ANIMATION_FRAME_TIMEOUT);
};
复制代码

这个函数咱们直接就经过函数名来理解,也就是调用requestAnimationFrame,若是超时了就改成普通调用。dom

在接下来部份内容,咱们须要预先了解requestAnimationFrame和eventLoop的知识异步

animationTick
var animationTick = function(rafTime) {
    // scheduledHostCallback也就是callback
    if (scheduledHostCallback !== null) {
      // 这里是连续递归调用,直到scheduledHostCallback === null
      // scheduledHostCallback会在messageChannel的port1的回调中设为null
      // 由于requestAnimationFrameWithTimeout会加入event loop,因此这里不是普通递归,而是每一帧执行一次
      // 注意当下一帧执行了animationTick时,以前的animationTick已经计算出了nextFrameTime
      requestAnimationFrameWithTimeout(animationTick);
    } else {
      // No pending work. Exit.
      isAnimationFrameScheduled = false;
      return;
    }
    // 保持浏览器能保持每秒30帧,那么每帧就是33毫秒
    // activeFrameTime在模块顶部定义,初始值为33
    // previousFrameTime的初始值也是33
    // nextFrameTime就是此方法到下一帧以前能够执行多少时间
    // 若是第一次执行,nextFrameTime确定是很大的,由于frameDeadline为0
    // rafTime是当前时间戳
    // 当第一次执行,nextFrameTime的值是一个包含当前时间戳,很大的值
    // 当不是第一次执行frameDeadline在后面已经赋值为rafTime + activeFrameTime
    // 也就是这个公式为new_rafTime - (old_rafTime + old_activeFrameTime) + new_activeFrameTime
    // 也就是(new_rafTime - old_rafTime) + (new_activeFrameTime - old_activeFrameTime)
    // 当通常状况(也就是不走近分支1)的状况,new_activeFrameTime === old_activeFrameTime
    // 因此nextFrameTime === (new_rafTime - old_rafTime)
    // 也就是两个requestAnimationFrameWithTimeout之间的时间差,即一帧所走过的时间
    // 当走过两帧以后,发现nextFrameTime和nextFrameTime的时间都小于activeFrameTime,则断定当前平台的帧数更高(每帧的时间更短)
    // 则走分支1修改activeFrameTime
    var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
    if (
      nextFrameTime < activeFrameTime &&
      previousFrameTime < activeFrameTime
    ) {
      // TODO 分支1
      if (nextFrameTime < 8) {
        // Defensive coding. We don't support higher frame rates than 120hz.
        // If the calculated frame time gets lower than 8, it is probably a bug.
        nextFrameTime = 8;
      }
      // 这里试探性的设置了activeFrame,由于在某些平台下,每秒的帧数可能更大,例如vr游戏这种状况
      // 设置activeFrameTime为previousFrameTime和nextFrameTime中的较大者
      activeFrameTime =
        nextFrameTime < previousFrameTime ? previousFrameTime : nextFrameTime;
    } else {
      previousFrameTime = nextFrameTime;
    }
    frameDeadline = rafTime + activeFrameTime;
    // isMessageEventScheduled的值也是在port1的回调中设置为false
    // isMessageEventScheduled的意义就是每一帧的animationTick是否被执行完
    // animationTick -> port.postMessage(设置isMessageEventScheduled为false) -> animationTick
    // 防止port.postMessage被重复调用(应该是在requestAnimationFrameWithTimeout超时的时候会出现的状况
    // 由于postMessage也是依赖event loop,可能会有竞争关系
    if (!isMessageEventScheduled) {
      isMessageEventScheduled = true;
      // port就是port1
      // postMessage是event loop下一个tick使用,因此就是frameDeadline中,其实留了空闲时间给浏览器执行动画渲染
      // 举个例子: 假设当前浏览器为30帧,则每帧33ms,frameDeadline为currentTime + 33,当调用了port.postMessage,当前tick的js线程就变为空了
      // 这时候就会留给浏览器部分时间作动画渲染,因此实现了requestIdleCallback的功能
      // port.postMessage是留给空出js线程的关键
      port.postMessage(undefined);
    }
  };
复制代码

中间部分nextFrameTIme的判断是React检查帧数的计算,咱们先忽略,关注总体。 animationTick一开始直接scheduledHostCallback是否为null,不然就继续经过requestAnimationFrameWithTimeout调用animationTick自身,这是一个逐帧执行的递归。意思就是这个递归在浏览器在渲染下一帧的时候,才会调用再次调用animationTick。 也就是在animationTick的调用requestAnimationFrameWithTimeout(animationTick)以后,后面的代码依然有时间能够执行。由于递归会在下一帧由浏览器调用。而在animationTick最后的代码调用了port.postMessage,这是一个一个浏览器提供的APIMessageChannel,主要用于注册的两端port之间相互通信,有兴趣的读者能够本身查查。MessageChannel的通信每次调用都是异步的,相似于EventListener,也就是,当调用port.postMessage,也就是告诉浏览器当前EventLoop的任务执行完了,浏览器能够检查一下如今如今有没有别的任务进来(例如动画或者用户操做),而后插入下一个EventLoop中。(固然在EventLoop的任务队列中,animationTick剩余的代码优先级会比动画及用户操做更高,由于排序排在前面。可是其实后面的代码也会有根据帧时间是否足够,执行让出线程的操做) 递归的流程以下图 函数

AnimationTick
了解了整个 AnimationTick的流程,咱们接下来看看具体的代码,AnimationTick的参数rafTime是当前的时间戳,activeFrameTime是React假设每一帧的时间,默认值为33,33是浏览器在每秒30帧的状况下,每一帧的时间。frameDeadline默认值为0。 咱们先跳过中间判断看一下frameDeadline的计算公式

frameDeadline = rafTime + activeFrameTime;
复制代码

因此frameDeadline是当前帧结束的时间。 nextFrameTime则是下一帧预计剩余时间,咱们看nextFrameTime的计算公式,

// 此处的rafTime是下一帧执行时的currentTime
var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
复制代码

currentTime - 任务的expirationTime + 每个帧的时间,也就是 每一帧的时间 - 任务预计花费的实际,因此nextFrameTime是预计的下一帧的剩余时间。 当咱们执行两帧事后,previousFrameTime和nextFrameTime都计算出值,咱们就有可能走进中间的判断若是先后两帧的时间都比默认设置的activeFrameTime小,也就是当前代码执行平台的帧数可能比30帧更高,因此设置activeFrameTime为测试出的新值。这种状况可能出如今VR这种对帧数要求更高的环境。 接下面咱们判断isMessageEventScheduled的布尔值,这是为了防止保证port.postMessage(undefined);在每一帧只调用一次。

channel.port1.onmessage(idleTick)

在AnimationTick中调用port.postMessage(undefined);以后,咱们实际上进入了channel.port1的回调函数,

channel.port1.onmessage = function(event) {
    // 设置为false,防止animationTick的竞争关系
    isMessageEventScheduled = false;

    var prevScheduledCallback = scheduledHostCallback;
    var prevTimeoutTime = timeoutTime;
    scheduledHostCallback = null;
    timeoutTime = -1;

    var currentTime = getCurrentTime();

    var didTimeout = false;
    // 说明超过了activeFrameTime的实际(默认值33
    // 说明这一帧没有空闲时间,而后检查任务是否过时,过时的话就设置didTimeout,用于后面强制执行
    if (frameDeadline - currentTime <= 0) {
      // There's no time left in this idle period. Check if the callback has
      // a timeout and whether it's been exceeded.
      // 查看任务是否过时,过时则强行更新
      // timeoutTime就是当时的CurrentTime + timeout
      // timeout是scheduleCallbackWithExpirationTime传进来的
      // 至关于currentTimeStamp + expirationTIme
      if (prevTimeoutTime !== -1 && prevTimeoutTime <= currentTime) {
        // Exceeded the timeout. Invoke the callback even though there's no
        // time left.
        // 这种过时的状况有可能已经掉帧了
        didTimeout = true;
      } else {
        // 没有超时则等待下一帧再执行
        // No timeout.
        // isAnimationFrameScheduled这个变量就是判断是否在逐帧执行animationTick
        // 开始设置animationTick时设置为true,animationTick结束时设置为false
        if (!isAnimationFrameScheduled) {
          // Schedule another animation callback so we retry later.
          isAnimationFrameScheduled = true;
          requestAnimationFrameWithTimeout(animationTick);
        }
        // Exit without invoking the callback.
        // 由于上一个任务没有执行完,设置回原来的值,等animationTick继续处理scheduledHostCallback
        scheduledHostCallback = prevScheduledCallback;
        timeoutTime = prevTimeoutTime;
        return;
      }
    }
复制代码

此处代码用了React中经常使用的命名方式prevXXXX,通常是在某个流程之中,先保留以前的值,在执行完某个操做以后,再还原某个值,提供给别的代码告诉本身正在处理的阶段。例如

var prevScheduledCallback = scheduledHostCallback;
    scheduledHostCallback = null;
    ...
    ...
    // 还原
    scheduledHostCallback = prevScheduledCallback;
复制代码

整个回调函数其实比较简单,只有几个分支

image.png

flushWork
function flushWork(didTimeout) {
  // didTimeout是指任务是否超时
  // Exit right away if we're currently paused

  if (enableSchedulerDebugging && isSchedulerPaused) {
    return;
  }

  isExecutingCallback = true;
  const previousDidTimeout = currentDidTimeout;
  currentDidTimeout = didTimeout;
  try {
    if (didTimeout) {
      // Flush all the expired callbacks without yielding.
      while (
        firstCallbackNode !== null &&
        !(enableSchedulerDebugging && isSchedulerPaused)
      ) {
        // TODO Wrap in feature flag
        // Read the current time. Flush all the callbacks that expire at or
        // earlier than that time. Then read the current time again and repeat.
        // This optimizes for as few performance.now calls as possible.
        var currentTime = getCurrentTime();
        if (firstCallbackNode.expirationTime <= currentTime) {
          // 这个循环的意思是,遍历callbackNode链表,直到第一个没有过时的callback
          // 因此主要意义就是将全部过时的callback马上执行完
          do {
            // 这个函数有将callbackNode剥离链表并执行的功能, firstCallbackNode在调用以后会修改为为新值
            // 这里遍历直到第一个没有过时的callback
            flushFirstCallback();
          } while (
            firstCallbackNode !== null &&
            firstCallbackNode.expirationTime <= currentTime &&
            !(enableSchedulerDebugging && isSchedulerPaused)
          );
          continue;
        }
        break;
      }
    } else {
      // Keep flushing callbacks until we run out of time in the frame.
      if (firstCallbackNode !== null) {
        do {
          if (enableSchedulerDebugging && isSchedulerPaused) {
            break;
          }
          flushFirstCallback();
          // shouldYieldToHost就是比较frameDeadline和currentTime,就是当前帧还有时间的话,就一直执行
        } while (firstCallbackNode !== null && !shouldYieldToHost());
      }
    }
  } finally {
    isExecutingCallback = false;
    currentDidTimeout = previousDidTimeout;
    if (firstCallbackNode !== null) {
      // There's still work remaining. Request another callback.
      // callback链表还没所有执行完,继续
      // ensureHostCallbackIsScheduled也是会启动下一帧,因此不是连续调用
      // 同时,isHostCallbackScheduled决定了ensureHostCallbackIsScheduled的行为,
      // 在此分支中isHostCallbackScheduled === true, 因此ensureHostCallbackIsScheduled会执行一个cancelHostCallback函数
      // cancelHostCallback设置scheduledHostCallback为null,能够令上一个animationTick中止
      ensureHostCallbackIsScheduled();
    } else {
      // isHostCallbackScheduled这个变量只会在ensureHostCallbackIsScheduled中被设置为true
      // 这个变量的意义多是表明,是否全部任务都被flush了?,由于只有firstCallbackNode === null的状况下才会设为false
      isHostCallbackScheduled = false;
    }
    // Before exiting, flush all the immediate work that was scheduled.
    flushImmediateWork();
  }
}
复制代码

flushFirstCallback

function flushFirstCallback() {
  var flushedNode = firstCallbackNode;

  // Remove the node from the list before calling the callback. That way the
  // list is in a consistent state even if the callback throws.
  var next = firstCallbackNode.next;
  // 这里是从链表中删除firstCallbackNode的处理
  if (firstCallbackNode === next) {
    // 这种状况,链表只有一个元素,直接清空
    // This is the last callback in the list.
    firstCallbackNode = null;
    next = null;
  } else {
    // 这个操做就是从链表中删除掉firstCallbackNode
    var lastCallbackNode = firstCallbackNode.previous;
    firstCallbackNode = lastCallbackNode.next = next;
    next.previous = lastCallbackNode;
  }

  flushedNode.next = flushedNode.previous = null;

  // Now it's safe to call the callback.
  // 像下面这种,先将currentXXX赋值给previousXXX,而后再讲previousXXX赋值给currentXXX,多是由于同时还有别的地方须要使用到currentXXX,留意一下
  // 也有多是要保证代码执行成功以后,才修改currentXXX的值
  var callback = flushedNode.callback;
  var expirationTime = flushedNode.expirationTime;
  var priorityLevel = flushedNode.priorityLevel;
  var previousPriorityLevel = currentPriorityLevel;
  var previousExpirationTime = currentExpirationTime;
  currentPriorityLevel = priorityLevel;
  currentExpirationTime = expirationTime;
  var continuationCallback;
  try {
    continuationCallback = callback();
  } finally {
    currentPriorityLevel = previousPriorityLevel;
    currentExpirationTime = previousExpirationTime;
  }

  // A callback may return a continuation. The continuation should be scheduled
  // with the same priority and expiration as the just-finished callback.
  if (typeof continuationCallback === 'function') {
    var continuationNode: CallbackNode = {
      callback: continuationCallback,
      priorityLevel,
      expirationTime,
      next: null,
      previous: null,
    };

    // Insert the new callback into the list, sorted by its expiration. This is
    // almost the same as the code in `scheduleCallback`, except the callback
    // is inserted into the list *before* callbacks of equal expiration instead
    // of after.
    // 这个链表插入顺序的区别在于,遇到expirationTime相等的element,scheduleCallback会设置在该element后面
    // 而此函数会设置在该element前面
    if (firstCallbackNode === null) {
      // This is the first callback in the list.
      firstCallbackNode = continuationNode.next = continuationNode.previous = continuationNode;
    } else {
      var nextAfterContinuation = null;
      var node = firstCallbackNode;
      do {
        // 和scheduleCallback函数惟一的区别就是这个等号
        if (node.expirationTime >= expirationTime) {
          // This callback expires at or after the continuation. We will insert
          // the continuation *before* this callback.
          nextAfterContinuation = node;
          break;
        }
        node = node.next;
      } while (node !== firstCallbackNode);

      if (nextAfterContinuation === null) {
        // No equal or lower priority callback was found, which means the new
        // callback is the lowest priority callback in the list.
        nextAfterContinuation = firstCallbackNode;
      } else if (nextAfterContinuation === firstCallbackNode) {
        // The new callback is the highest priority callback in the list.
        firstCallbackNode = continuationNode;
        ensureHostCallbackIsScheduled();
      }

      var previous = nextAfterContinuation.previous;
      previous.next = nextAfterContinuation.previous = continuationNode;
      continuationNode.next = nextAfterContinuation;
      continuationNode.previous = previous;
    }
  }
}
复制代码
核心变量

下面记录了部分核心变量的解释,只做为帮助阅读使用

P.S. 在下面变量的命名中,包含hostCallback,host能够理解为主要的。包含scheduled能够理解为是否正在处理


callback环形链表
基础结构
  • firstCallbackNode 环形链表的起点
  • firstCallbackNode.next 下一个元素
  • firstCallbackNode.previous 上一个元素
元素排列顺序
*           head
    *    next7         next1
    *  next6              next2
    *    next5         next3
    *           next4
    *
复制代码

假设咱们的链表有8个元素,他们会按照expirationTime从小到大排序,也就是head(firstCallbackNode)的expirationTime最小,next7的expirationTime最大。

TODO:补充expirationTime和优先级大小的关系以及贴上issue地址

在scheduler的代码中,咱们会常常看到一个判断

firstCallbackNode === null // 若是成立则链表为空
复制代码

这个其实就是在判断链表是否为空,由于任何对链表的删除和增长操做,都会更新firstCallbackNode的值,保证firstCallbackNode不为null,除非整个链表已经没有任何元素了。

currentDidTimeout

boolean 用于判断当前callback是否超时。


isExecutingCallback

boolean 判断整个scheduler是否正在flushcallback,flush能够理解为执行callback。 这个变量在函数flushWork中设置为true,当callback执行完以后设置为false


isHostCallbackScheduled

boolean 判断是否进入了requestHostCallback,requestHostCallback会开启animationTick,进行每个帧的任务调度。当调用到flushWork直到链表中的callback处理结束,设为false。 主要用于当一个callback处理后产生continuationCallback时,而这个continuationCallback再次成为firstCallbackNode(也就是expirationTime最小的callback),须要从新调用ensureHostCallbackIsScheduled时,将当前的相关变量重置

scheduledHostCallback = null;
    isMessageEventScheduled = false;
    timeoutTime = -1;
复制代码

scheduledHostCallback

function 就是函数flushWork,这个变量可能会被置null,用于animationTick判断是否停止递归


isMessageEventScheduled

在animationTick中,判断经过messageChannel传输的回调是否执行了


timeoutTime

当前正在处理的callback(firstCallbackNode)的expirationTime


isAnimationFrameScheduled

是否处于animationTick的逐帧递归中

isFlushingHostCallback

是否正在执行flushWork(也就是是否正在处理callback)

相关文章
相关标签/搜索