zlx362211854 / daily-study

每日一个知识点总结,以issue的形式体现
10 stars 6 forks source link

46. 如何获取页面渲染时间? #84

Open goldEli opened 5 years ago

goldEli commented 5 years ago
<script>
    var startTime = new Date().getTime()
    for (let i = 0; i < 100000; ++i) {
      const li = document.createElement('li')
      li.textContent = "This is li" + i
      document.body.append(li)  
    }

    // js 执行时间
    console.log(new Date().getTime() - startTime)

    // 渲染时间?
</script>

第一个 console 打印出了 js 代码的执行时间。 那如何获取页面渲染的时间呢?

goldEli commented 5 years ago
<script>
    var startTime = new Date().getTime()
    for (let i = 0; i < 100000; ++i) {
      const li = document.createElement('li')
      li.textContent = "This is li" + i
      document.body.append(li)  
    }

    // js 执行时间
    const excuteTime = new Date().getTime() - startTime
    console.log(excuteTime)

    // 渲染时间
    setTimeout(() => {
      const renderTime = new Date().getTime() - startTime - excuteTime
      console.log(renderTime)
    }, 0)  
</script>

如上代码,末尾通过 setTimeout 就可以打印出渲染时间。

为什么呢?

渲染进程和 JavaScript 执行进程是互斥的

也就是说当 JavaScript 代码在执行时,渲染就会停止,渲染的时候,代码执行就会停止。

试想一下如果不互斥,那么 JavaScript 代码在操作 DOM 的同时渲染进程又在渲染页面,那么结果是无法预期的

宏任务结束,渲染进程开始渲染

每个执行栈执行的内容被称为宏任务,当执行栈执行完毕,即宏任务结束,渲染进程开始渲染。

以上面的代码为例,第一个宏任务的执行栈中有 setTimeout,那么它的回调就是第二个宏任务,所以在渲染结束后,执行 setTimeout 的回调就能计算出渲染时间。

如果把 setTimeout 改成 Promise.then 也可以实现吗?

不能。

Promise.then 属于微任务,一个宏任务执行完毕后需要执行完微任务队列的所有内容才开始渲染。

如下图:

WeWork Helper20190925095408

zlx362211854 commented 5 years ago

performance api

导航定时可以很容易地测量网站的实际速度和性能,并找到需要调优的问题区域。例如,导航计时可以帮助您精确地监视客户页面导航并跟踪用户活动的计时,从而帮助您定位延迟问题。然后,您可以更容易地确定性能瓶颈,并找到有效的解决方案来减少延迟,提高网站的速度和效率。旧的性能度量系统不能提供导航定时所支持的完整的端到端延迟图。

performance api有如下属性:

属性 简介
connectEnd Time when server connection is finished.
connectStart Time just before server connection begins.
domComplete Time just before document readiness completes.
domContentLoadedEventEnd Time after DOMContentLoaded event completes.
domContentLoadedEventStart Time just before DOMContentLoaded starts.
domInteractive Time just before readiness set to interactive.
domLoading Time just before readiness set to loading.
domainLookupEnd Time after domain name lookup.
domainLookupStart Time just before domain name lookup.
fetchStart Time when the resource starts being fetched.
loadEventEnd Time when the load event is complete.
loadEventStart Time just before the load event is fired.
navigationStart Time after the previous document begins unload.
redirectCount Number of redirects since the last non-redirect.
redirectEnd Time after last redirect response ends.
redirectStart Time of fetch that initiated a redirect.
requestStart Time just before a server request.
responseEnd Time after the end of a response or connection.
responseStart Time just before the start of a response.
timing Reference to a performance timing object.
navigation Reference to performance navigation object.
performance Reference to performance object for a window.
type Type of the last non-redirect navigation event.
unloadEventEnd Time after the previous document is unloaded.
unloadEventStart Time just before the unload event is fired.

查看更多

计算页面渲染时间,我们需要使用三个属性:

  1. navigationStart: 浏览器处理当前网页的启动时间
  2. domContentLoadedEventEnd:当前网页dom树内容加载完毕时间,此时不包含正在加载的图片资源等,对应document.ready方法
  3. loadEventEnd:当前网页完全加载完毕,包括图片等资源,对应window.onload方法
  4. performance.now() 计算网页从performance.timing.navigationStart到当前时间的毫秒数。

所以我们想要获得页面dom树加载时间和完整页面加载时间:

window.onload = function () {
    var DomloadTime = window.performance.timing.domContentLoadedEventEnd-window.performance.timing.navigationStart; 
    var PageloadTime = performance.now(); 
    console.log('Dom load time is ',DomloadTime, PageloadTime);
}

image 可见 页面完全加载会比dom树加载慢一些。

nanslee commented 5 years ago

const { navigationStart, domComplete } = performance.timing; DomloadTime = domComplete - navigationStart