关于前端:首屏时间你说你优化了那你倒是计算出给给我看啊

36次阅读

共计 4466 个字符,预计需要花费 12 分钟才能阅读完成。

前言

大家好,我是林三心,用最通俗易懂的话讲最难的知识点 是我的座右铭,根底是进阶的前提 是我的初心

背景

当咱们在做我的项目的性能优化的时候,优化首屏工夫 是一个避不过来的优化方向,然而又有多少人想过这两个货色的区别呢:

  • 白屏工夫
  • 首屏工夫

并且这两个工夫的计算形式又有什么区别呢?接下来我就给大家讲一下吧!

白屏工夫

是什么?

白屏工夫指的是:页面开始显示内容的工夫 。也就是: 浏览器显示第一个字符或者元素的工夫

怎么算?

咱们只须要晓得浏览器开始显示内容的工夫点,即页面白屏完结工夫点即可获取到页面的白屏工夫。

因而,咱们通常认为浏览器开始渲染 <body> 标签或者解析完 <head> 标签的时刻就是页面白屏完结的工夫点。

  • 浏览器反对 performance.timing

    <head>
    <title>Document</title>
    </head>
    <script type="text/javascript">
    // 白屏工夫完结点
    var firstPaint = Date.now()
    var start = performance.timing.navigationStart
    console.log(firstPaint - start)
    </script>
  • 浏览器不反对 performance.timing

    <head>
    <title>Document</title>
    <script type="text/javascript">
      window.start = Date.now();
    </script>
    </head>
    <script type="text/javascript">
    // 白屏工夫完结点
    var firstPaint = Date.now()
    console.log(firstPaint - window.start)
    </script>

    首屏工夫

    是什么?

    首屏工夫是指用户关上网站开始,到浏览器首屏内容渲染实现的工夫。对于用户体验来说,首屏工夫是用户对一个网站的重要体验因素

为什么不间接用生命周期?

有些小伙伴会说:为啥不间接在 App.vue 的 mounted 生命周期里计算工夫呢?大家能够看看,官网说了 mounted 执行并不代表首屏所有元素加载结束,所以 mounted 计算出来的工夫会偏短。

为什么不间接用 nextTick?

nextTick 回调的时候,首屏的 DOM 都渲染进去了,然而计算 首屏工夫 并不需要渲染所有 DOM,所以计算出来的工夫会偏长

怎么算?

咱们须要利用 MutationObserver 监控 DOM 的变动,监控每一次 DOM 变动的 分数,计算的规定为:
(1 + 层数 * 0.5),我举个例子:

<body>
    <div>
      <div>1</div>
      <div>2</div>
    </div>
</body>

以上 DOM 构造的分数为:

1.5 + 2 + 2.5 + 2.5 = 8.5(分)

其实在首屏的加载中,会波及到 DOM 的减少、批改、删除,所以会触发屡次 MutationObserver ,所以会统计出不同阶段的 score,咱们把这些 score 寄存在一个数组 observerData 中,前面大有用途

首屏工夫实际

当初咱们开始计算首屏工夫吧!

前置筹备

  • index.html:html 页面

    <!DOCTYPE html>
    <html lang="en">
    <head> </head>
    <body>
      <div>
        <div>
          <div>1</div>
          <div>2</div>
        </div>
        <div>3</div>
        <div>4</div>
      </div>
      <ul id="ulbox"></ul>
    </body>
    <script src="./computed.js"></script>
    <script src="./request.js"></script>
    </html>
  • computed.js :计算首屏工夫的文件

    const observerData = []
    
    let observer = new MutationObserver(() => {
    // 计算每次 DOM 批改时,间隔页面刚开始加载的工夫
    const start = window.performance.timing.navigationStart
    const time = new Date().getTime() - start
    
    const body = document.querySelector('body')
    const score = computedScore(body, 1)
    // 加到数组 observerData 中
    observerData.push({
      score,
      time
    })
    })
    observer.observe(
    document, {
      childList: true,
      subtree: true
    }
    )
    
    function computedScore(element, layer) {
    let score = 0
    const tagName = element.tagName
    // 排除这些标签的状况
    if (
      tagName !== 'SCRIPT' &&
      tagName !== 'STYLE' &&
      tagName !== 'META' &&
      tagName !== 'HEAD'
    ) {
      const children = element.children
      if (children && children.length) {
        // 递归计算分数
        for (let i = 0; i < children.length; i++) {score += computedScore(children[i], layer + 1)
        }
      }
    
      score += 1 + 0.5 * layer
    }
    return score
    }
  • request.js :模仿申请批改 DOM

    // 模仿申请列表
    const requestList = () => {return new Promise((resolve) => {setTimeout(() => {
        resolve(
          [1, 2, 3,
            4, 5, 6,
            7, 8, 9
          ]
        )
      }, 1000)
    })
    }
    
    const ulbox = document.getElementById('ulbox')
    
    // 模仿申请数据渲染列表
    const renderList = async () => {const list = await requestList()
    const fragment = document.createDocumentFragment()
    for (let i = 0; i < list.length; i++) {const li = document.createElement('li')
      li.innerText = list[i]
      fragment.appendChild(li)
    }
    ulbox.appendChild(fragment)
    }
    
    // 模仿对列表进行轻微批改
    const addList = async () => {const li = document.createElement('li')
    li.innerText = '加上去'
    ulbox.appendChild(li)
    }
    
    (async () => {
    // 模仿申请数据渲染列表
    await renderList()
    // 模仿对列表进行轻微批改
    addList()})()

observerData

当咱们所有准备就绪后运行代码,咱们取得了 observerData ,咱们看看它长什么样?

计算首屏工夫

咱们怎么依据 observerData 来计算首屏工夫呢?咱们能够这么算:下次分数比上次分数减少幅度最大的工夫作为首屏工夫

很多人会问了,为什么不是取最初一项的工夫来当做首屏工夫呢?大家要留神了:首屏并不是所有 DOM 都渲染,我就拿刚刚的代码来举例吧,咱们渲染完了列表,而后再去减少一个 li,那你是感觉哪个时间段算是首屏呢?应该是渲染完列表后算首屏实现,因为前面只减少了一个 li,分数的涨幅较小,能够忽略不计

所以咱们开始计算吧:

const observerData = []

let observer = new MutationObserver(() => {
  // 计算每次 DOM 批改时,间隔页面刚开始加载的工夫
  const start = window.performance.timing.navigationStart
  const time = new Date().getTime() - start
  const body = document.querySelector('body')
  const score = computedScore(body, 1)
  observerData.push({
    score,
    time
  })

  // complete 时去调用 unmountObserver
  if (document.readyState === 'complete') {
    // 只计算 10 秒内渲染工夫
    unmountObserver(10000)
  }
})
observer.observe(
  document, {
    childList: true,
    subtree: true
  }
)

function computedScore(element, layer) {
  let score = 0
  const tagName = element.tagName
  // 排除这些标签的状况
  if (
    tagName !== 'SCRIPT' &&
    tagName !== 'STYLE' &&
    tagName !== 'META' &&
    tagName !== 'HEAD'
  ) {
    const children = element.children
    if (children && children.length) {
      // 递归计算分数
      for (let i = 0; i < children.length; i++) {score += computedScore(children[i], layer + 1)
      }
    }

    score += 1 + 0.5 * layer
  }
  return score
}

// 计算首屏工夫
function getFirstScreenTime() {
  let data = null
  for (let i = 1; i < observerData.length; i++) {
    // 计算幅度
    const differ = observerData[i].score - observerData[i - 1].score
    // 取最大幅度,记录对应工夫
    if (!data || data.rate <= differ) {
      data = {time: observerData[i].time,
        rate: differ
      }
    }
  }
  return data
}

let timer = null

function unmountObserver(delay) {if (timer) return
  timer = setTimeout(() => {
    // 输入首屏工夫
    console.log(getFirstScreenTime())
    // 终止 MutationObserver 的监控
    observer.disconnect()
    observer = null
    clearTimeout(timer)
  }, delay)
}

计算出首屏工夫 1020ms

总结

我这个计算方法其实很多破绽,没把删除元素也思考进去,然而想让大家晓得计算首屏工夫的计算思维,这才是最重要的,心愿大家能了解这个计算思维

结语

我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜爱前端,想学习前端,那咱们能够交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】

正文完
 0