浏览器原理 15 # WebAPI:setTimeout 是如何实现的?

简介: 浏览器原理 15 # WebAPI:setTimeout 是如何实现的?

用法

setTimeout 定时器,用来指定某个函数在多少毫秒之后执行。

function showName(){
  console.log("凯小默")
}
var timerID = setTimeout(showName, 200);



浏览器怎么实现 setTimeout


   渲染进程中所有运行在主线程上的任务都需要先添加到消息队列,然后事件循环系统再按照顺序执行消息队列中的任务。不过通过定时器设置回调函数有点特别,它们需要在指定的时间间隔内被调用,但消息队列中的任务是按照顺序执行的,所以为了保证回调函数能在指定时间内执行,不能将定时器的回调函数直接添加到消息队列中。


在规定时间内怎么执行定时器设置的回调事件?


   在 Chrome 中除了正常使用的消息队列之外,还有另外一个消息队列,这个队列中维护了需要延迟执行的任务列表,包括了定时器和 Chromium 内部一些需要延迟执行的任务。所以当通过 JavaScript 创建一个定时器时,渲染进程会将该定时器的回调任务添加到延迟队列中。



参考资料:Chromium 中关于队列部分的源码


源码中延迟执行队列的定义:

DelayedIncomingQueue delayed_incoming_queue;



1.当通过 JavaScript 调用 setTimeout 设置回调函数的时候,渲染进程将会创建一个回调任务

struct DelayTask{
  int64 id;
  CallBackFunction cbf;
  int start_time;
  int delay_time;
};
DelayTask timerTask;
timerTask.cbf = showName;
timerTask.start_time = getCurrentTime(); //获取当前时间
timerTask.delay_time = 200;//设置延迟执行时间


  1. 创建好回调任务之后,再将该任务添加到延迟执行队列中
delayed_incoming_queue.push(timerTask);



消息循环系统是怎么触发延迟队列的?


在消息循环的代码中加入执行延迟队列的代码


ProcessDelayTask 函数:专门用来处理延迟执行任务的,它会根据发起时间和延迟时间计算出到期的任务,然后依次执行这些到期的任务,等到期的任务执行完成之后,再继续下一个循环过程。

void ProcessTimerTask(){
  //从delayed_incoming_queue中取出已经到期的定时器任务
  //依次执行这些任务
}
TaskQueue task_queue;
void ProcessTask();
bool keep_running = true;
void MainTherad(){
  for(;;){
    //执行消息队列中的任务
    Task task = task_queue.takeTask();
    ProcessTask(task);
    //执行延迟队列中的任务
    ProcessDelayTask()
    if(!keep_running) //如果设置了退出标志,那么直接退出线程循环
        break; 
  }
}


设置一个定时器,JavaScript 引擎会返回一个定时器的 ID。

取消定时器:

clearTimeout(timer_id)



浏览器内部实现取消定时器的操作:从 delayed_incoming_queue 延迟队列中,通过 ID 查找到对应的任务,然后再将其从队列中删除掉就可以了。



使用 setTimeout 的注意事项


1. 如果当前任务执行时间过久,会影响定时器任务的执行


例子:

function bar() {
    console.log('bar')
}
function foo() {
    setTimeout(bar, 0);
    for (let i = 0; i < 5000; i++) {
        let i = 5+8+8+8
        console.log(i)
    }
}
foo()


长任务导致定时器被延后执行:


20210413100223657.png


2. 如果 setTimeout 存在嵌套调用,那么系统会设置最短时间间隔为 4 毫秒

例子:

function cb() { setTimeout(cb, 0); }
setTimeout(cb, 0);



循环嵌套调用 setTimeout:

20210413100615295.png


因为在 Chrome 中,定时器被嵌套调用 5 次以上,系统会判断该函数方法被阻塞了,如果定时器的调用时间间隔小于 4 毫秒,那么浏览器会将每次调用的时间间隔设置为 4 毫秒。

参考资料:Chromium 实现 4 毫秒延迟的代码

static const int kMaxTimerNestingLevel = 5;
// Chromium uses a minimum timer interval of 4ms. We'd like to go
// lower; however, there are poorly coded websites out there which do
// create CPU-spinning loops.  Using 4ms prevents the CPU from
// spinning too busily and provides a balance between CPU spinning and
// the smallest possible interval timer.
static constexpr base::TimeDelta kMinimumInterval = base::TimeDelta::FromMilliseconds(4);


base::TimeDelta interval_milliseconds =
      std::max(base::TimeDelta::FromMilliseconds(1), interval);
  if (interval_milliseconds < kMinimumInterval &&
      nesting_level_ >= kMaxTimerNestingLevel)
    interval_milliseconds = kMinimumInterval;
  if (single_shot)
    StartOneShot(interval_milliseconds, FROM_HERE);
  else
    StartRepeating(interval_milliseconds, FROM_HERE);


3. 未激活的页面,setTimeout 执行最小间隔是 1000 毫秒

如果标签不是当前的激活标签,那么定时器最小的时间间隔是 1000 毫秒,目的是为了优化后台页面的加载损耗以及降低耗电量


4. 延时执行时间有最大值

Chrome、Safari、Firefox 都是以 32 个 bit 来存储延时值的,32bit 最大只能存放的数字是 2147483647 毫秒,这就意味着,如果 setTimeout 设置的延迟值大于 2147483647 毫秒(大约 24.8 天)时就会溢出,那么相当于延时值被设置为 0 了,这导致定时器会被立即执行。


例子:

20210413101312667.png


5. 使用 setTimeout 设置的回调函数中的 this 不符合直觉

如果被 setTimeout 推迟执行的回调函数是某个对象的方法,那么该方法中的 this 关键字将指向全局环境,而不是定义时所在的那个对象。

var name= 1;
var MyObj = {
  name: 2,
  showName: function(){
    console.log(this.name);
  }
}
setTimeout(MyObj.showName,1000)

20210413120230491.png

第一种是将MyObj.showName放在匿名函数中执行:

//箭头函数
setTimeout(() => {
    MyObj.showName()
}, 1000);
//或者function函数
setTimeout(function() {
  MyObj.showName();
}, 1000)


20210413120411325.png




第二种是使用 bind 方法

setTimeout(MyObj.showName.bind(MyObj), 1000)


20210413120501204.png

目录
相关文章
|
1天前
|
存储 缓存 前端开发
浏览器缓存工作原理是什么?
浏览器缓存工作原理是什么?
|
1天前
|
存储 安全 前端开发
浏览器跨窗口通信:原理与实践
浏览器跨窗口通信:原理与实践
56 0
|
1天前
|
消息中间件 JavaScript 前端开发
前端秘法进阶篇----这还是我们熟悉的浏览器吗?(浏览器的渲染原理)
前端秘法进阶篇----这还是我们熟悉的浏览器吗?(浏览器的渲染原理)
|
1天前
|
消息中间件 前端开发 Java
【面试题】前端必修-浏览器的渲染原理
【面试题】前端必修-浏览器的渲染原理
|
5月前
|
Web App开发 JavaScript 前端开发
从浏览器原理出发聊聊Chrome插件
本文从浏览器架构演进、插件运行机制、插件基本介绍和一些常见的插件实现思路几个方向聊聊Chrome插件。
790 0
|
8月前
|
安全 算法 网络协议
浏览器基础原理-安全: HTTPS
浏览器基础原理-安全: HTTPS
63 0
|
8月前
|
Web App开发 存储 监控
浏览器基础原理-安全: 渲染进程-安全沙盒
浏览器基础原理-安全: 渲染进程-安全沙盒
39 0
|
1天前
|
JavaScript
浏览器插件crx文件--JS混淆与解密
浏览器插件crx文件--JS混淆与解密
21 0
|
1天前
|
JavaScript 前端开发 UED
JS:如何获取浏览器窗口尺寸?
JS:如何获取浏览器窗口尺寸?
53 1
|
1天前
|
JavaScript 前端开发 算法
Node.js中的process.nextTick与浏览器环境中的nextTick有何不同?
Node.js中的process.nextTick与浏览器环境中的nextTick有何不同?

热门文章

最新文章

相关实验场景

更多