共计 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
总结
我这个计算方法其实很多破绽,没把删除元素也思考进去,然而想让大家晓得计算首屏工夫的计算思维,这才是最重要的,心愿大家能了解这个计算思维
结语
我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜爱前端,想学习前端,那咱们能够交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】