极客时间返利平台,你可以在上边通过山月的链接购买课程,并添加我的微信 (shanyue94) 领取返现。
每天晚上九点 B站讲解前端工程化直播,并解答相关问题。

# setTimeout 为什么最小只能设置 4ms,如何实现一个 0ms 的 setTimeout?

Issue

欢迎在 Gtihub Issue 中回答此问题: Issue 708 (opens new window)

postMessage

blink 引擎的 DOMTimer 类源码 (opens new window) 前几天刚好找了一下源码,在 47-49 行设置了kMaxTimerNestingLevelkMinimumInterval 两个变量分别为 5 和 4, 分别表示最大的嵌套层数和最小的毫秒数 image 截取一下 DOMTimer 类的部分代码

DOMTimer::DOMTimer(ExecutionContext* context,
                   ScheduledAction* action,
                   base::TimeDelta timeout,
                   bool single_shot,
                   int timeout_id)
    : ExecutionContextLifecycleObserver(context),
      TimerBase(nullptr),
      timeout_id_(timeout_id),
      // Step 9:
      nesting_level_(context->Timers()->TimerNestingLevel()),
      action_(action) {
  DCHECK_GT(timeout_id, 0);

  // Step 10:
  if (timeout < base::TimeDelta())
    timeout = base::TimeDelta();

  // Steps 12 and 13:
  // Note: The implementation increments the nesting level before using it to
  // adjust timeout, contrary to what the spec requires crbug.com/1108877.
  IncrementNestingLevel();

  // Step 11:
  // Note: The implementation uses >= instead of >, contrary to what the spec
  // requires crbug.com/1108877.
  if (nesting_level_ >= kMaxTimerNestingLevel && timeout < kMinimumInterval)
    timeout = kMinimumInterval;

  // Select TaskType based on nesting level.
  TaskType task_type;
  if (timeout.is_zero()) {
    task_type = TaskType::kJavascriptTimerImmediate;
    DCHECK_LT(nesting_level_, kMaxTimerNestingLevel);
  } else if (nesting_level_ >= kMaxTimerNestingLevel) {
    task_type = TaskType::kJavascriptTimerDelayedHighNesting;
  } else {
    task_type = TaskType::kJavascriptTimerDelayedLowNesting;
  }
  MoveToNewTaskRunner(context->GetTaskRunner(task_type));

  // Clamping up to 1ms for historical reasons crbug.com/402694.
  timeout = std::max(timeout, base::TimeDelta::FromMilliseconds(1));

看代码中 setp11 的那部分,当嵌套层数大于 5 且 timeout 小于 4ms 时,timeout 才会等于 4ms 然后(代码最后一行),timeout 还会和 1ms 作比较取最大值,作为最终的 timeout

let timeouts = [];
const messageName = 'zero-settimeout'

function setTimeoutZero(fn) {
  timeouts.push(fn);
  window.postMessage(messageName, '*')
}

function handleMessage (evt) {
  if (evt.source == window && evt.data === messageName ) {
    if (timeouts.length > 0) {
      const f = timeouts.shift()
      f()
    }
  }
}

window.addEventListener('message', handleMessage)

window.zeroSettimeout = setTimeoutZero;
Last Updated: 11/27/2021, 10:11:48 AM