JS线程阻塞时间

转载自 https://juejin.cn/post/7126904541165027341

由于JS单线程的语言特性,浏览器js中大部分事件都是以异步回调的形式被处理。例如浏览器中对用户的输入进行响应,实际上是这样一个流程:

  1. 浏览器进程监听到用户的输入(键盘或鼠标)
  1. 创建相应的event对象
  1. event的回调事件推入下一事件循环
  1. 等待js线程完成当前事件循环
  1. 根据js事件模型,按照捕获阶段、目标阶段、冒泡阶段的顺序,依次执行相应的回调函数
  1. 假如事件(passive事件除外)的preventDefault没有被调用,那么浏览器才会执行默认的处理,如:输入框字符上屏

假如在4和5中有long task长时间占据js执行线程,那么1和6之间的间隔就会变大,用户会明显感知到页面的反馈不及时,也就是卡顿。

因此,假如我们需要对卡顿的频率和严重情况进行监控时,或是希望监控long task情况以保证研发质量时,就需要对js线程阻塞的时间进行监控。

获取js线程阻塞时间

那如何计算js线程的阻塞时间呢?我们可以转而计算:浏览器监听到用户输入的时间,和我们实际回调函数的执行时间,两者的差值就可以近似的看作是js线程被阻塞的时长。

事件创建时间

翻阅MDN的文档可以找到,Event上存在timeStamp属性,其值就是事件创建的时间戳。然而在chrome上实际尝试后可以看到,这个值远远小于Date.now()返回的时间戳,比如是16662.19999999553,这是为什么呢?

实际上,根据W3C规范中的事件模型对于该属性的描述

timeStamp of type DOMTimeStamp , readonly

Used to specify the time (in milliseconds relative to the epoch) at which the event was created. Due to the fact that some systems may not provide this information the value of timeStamp may be not available for all events. When not available, a value of 0 will be returned. Examples of epoch time are the time of the system start or 0:0:0 UTC 1st January 1970.

也就是说event.timeStamp是一个相对时间戳,单位是毫秒,代表的是事件的创建时间。假如无法获取创建时间,则读取到的是0。这里的相对并没有指定是UNIX毫秒时间戳,所以这个属性的实际上是一个高精时间戳(High Resolution Time)

高精时间戳

高精时间戳(High Resolution Time)是一个高精度的、最高可以精确到微秒级别的、不随系统时间偏移的时间戳。

Date.now()的精确程度只有毫秒级别,更重要的是,它是基于系统(本地)时间的,假如系统时间有偏差时(例如系统时间快/慢了数分钟)返回的时间戳也是有偏差的。而且由于读取的是本地时间,它并不能保证是单调递增的。假如某一时刻系统时间被修改,这一时刻之后的返回值是可能小于之前的返回值的。而高精时间戳可以保证单调递增,而且还是以恒定速率单调递增的。

高精时间戳也是一个相对时间戳,它的0时刻并不是固定的,而是上下文创建的时间。因此,高精时间戳也可以看作是相对performance.timeOrigin后经过的时间,而performance.timeOrigin代表的是上下文创建时的、相对于UNIX 0时刻的高精度时间戳。

当前时刻高精时间戳

performance上的now()方法返回的是当前时刻的高精时间戳,也就是相对performace.timeOrigin之后经过的时间。

计算js线程阻塞时间

因为event.timeStampperformance.now都是基于performance.timeOrigin的高精时间戳,因此它们可以直接相减,结果就是当前时刻事件创建时刻之间的差值,也就是js线程阻塞的时间了。

Demo

这里以<input>框与keydown事件举例

// 获取关键元素的dom节点
const dom = document.querySelector('input');
// 模拟q键有long task阻塞
dom.addEventListener('keydown', event => {
  if (event.key === 'q') {
    console.time('long task');
    const arr = Array.from({ length: 1000 * 1000 })
      .map((, i) => i)
  .sort( => Math.random() - 0.5);
console.timeEnd('long task');
  }
});

// 获取每次输入的js阻塞时间
dom.addEventListener('keydown', event => {
  const costTime = performance.now() - event.timeStamp;
  console.log('按键', event.key, 'js线程阻塞时间:', costTime);
});

// 获取关键元素的dom节点
const dom = document.querySelector('input');
// 模拟q键有long task阻塞
dom.addEventListener('keydown', event => {
  if (event.key === 'q') {
    console.time('long task');
    const arr = Array.from({ length: 1000 * 1000 })
      .map((, i) => i)
  .sort( => Math.random() - 0.5);
console.timeEnd('long task');
}
});

// 获取每次输入的js阻塞时间
dom.addEventListener('keydown', event => {
  const costTime = performance.now() - event.timeStamp;
  console.log('按键', event.key, 'js线程阻塞时间:', costTime);
});

可以看到,正常输入的阻塞时间不到1ms,但是当按下包含long task的q键时,不仅q键的第二个回调被前面的long task阻塞了,在q键后面键入的按键也被阻塞了,可以通过这种方法获取被阻塞的时长,而且计算出来的结果也和我们的体感对得上。

兼容性

兼容性方面不用顾虑,十年以内的浏览器版本基本都能使用,哪怕IE也能轻松支持。

event.timestamp

performance.now()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值