前言
嘿嘿,好久没写文章了,最近在上班当牛马,一有时间就来更新一下面试题了。
题目描述
有一个页面,有8个元素, 做了flex 布局,请计算性能指标的渲染总时间和第一个元素的渲染完时间.

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div class="flex-item">item 1</div>
<div class="flex-item">item 2</div>
<div class="flex-item">item 3</div>
<div class="flex-item">item 4</div>
<div class="flex-item">item 5</div>
<div class="flex-item">item 6</div>
<div class="flex-item">item 7</div>
<div class="flex-item">item 8</div>
</div>
<script src="./script.js"></script>
</body>
</html>
css
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
}
.container {
display: flex;
flex-wrap: wrap; /* 允许换行 */
justify-content: space-between; /* 元素之间均匀分布 */
}
.flex-item {
background-color: #4CAF50; /* 绿色背景 */
color: white; /* 白色文字 */
padding: 20px;
margin: 10px;
flex: 1 1 calc(25% - 20px); /* 每个元素占容器的25%宽度,减去边距 */
box-sizing: border-box; /* 包含内边距和边框在内的宽度计算 */
text-align: center; /* 文字居中 */
}
解题:
考察
该题考察的是你是否知道页面的渲染步骤:
1. 构建DOM树
当浏览器接收到HTML文档时,它会开始解析这个文档,并构建一个DOM(Document Object Model)树。DOM树是网页上所有元素的一个层次结构表示。每个HTML标签都会被转换为DOM树中的一个节点。
2. 加载外部资源
在构建DOM树的同时,浏览器还会下载页面中引用的所有外部资源,如CSS文件、JavaScript文件、图片等。这些资源可能会影响页面的最终布局和样式。
3. 构建CSSOM树
同时,浏览器也会解析所有的CSS规则,无论是内联的、包含在<style>
标签内的还是从外部CSS文件加载的,并根据这些规则构建CSS对象模型(CSSOM)。CSSOM与DOM一起决定了每个元素最终的样式。
4. 创建Render树
浏览器接下来会将DOM树和CSSOM树合并,生成一个Render树(也称为渲染树或框树)。Render树只包含需要显示在页面上的节点及其计算后的样式信息。例如,那些设置为display: none;
的元素不会出现在Render树中。
5. 布局阶段(Layout)
一旦Render树创建完成,浏览器就会进行布局处理,即确定每个节点在屏幕上的精确位置和大小。这一步骤被称为"重排"或"回流"。在这个阶段,浏览器会考虑各种因素,如视口尺寸、父容器尺寸等。
6. 绘制(Paint)
在布局之后,浏览器会进入绘制阶段,将Render树中的每个节点转换成屏幕上的实际像素。这包括文本、颜色、边框、阴影和其他视觉效果的绘制。此过程可能分为多个层来优化性能。
7. 合成(Composite)
最后,如果页面使用了合成技术(比如使用了CSS 3D变换或某些动画),不同的层会被组合在一起形成最终的图像。现代浏览器通常会在GPU上执行这一操作以提高效率。
代码:
js
function measureRenderTime() {
const startTime = performance.now(); // 计算开始时间
window.addEventListener('load', () => {
const loadEndTime = performance.now();
console.log(`页面渲染总时间: ${loadEndTime - startTime}ms`);
});
const allItems = document.querySelectorAll('.flex-item');
const firstElement = allItems[0]; // 第一个元素
const eightElement = allItems[7]; // 第八个元素(假设至少有8个)
function checkElement(element, label) {
if (!element) return; // 如果元素不存在,则返回
if (element.offsetWidth > 0 && element.offsetHeight > 0) { // 元素已经渲染了
const endTime = performance.now(); // 计算结束时间
const renderTime = endTime - startTime; // 计算渲染时间
console.log(`${label}元素渲染完成时间: ${renderTime}ms`); // 打印渲染时间
} else {
requestAnimationFrame(() => checkElement(element, label)); // 使用requestAnimationFrame代替setInterval
}
}
checkElement(firstElement, '第一个');
checkElement(eightElement, '第八个');
}
// 确保DOM完全加载后再执行
document.addEventListener('DOMContentLoaded', measureRenderTime);
解析:

-
performance.now()
: 一个高精度的时间测量方法,用来获取当前时间戳。通过比较不同时间点的值,可以精确地计算出某段代码或某个过程的执行时间。 -
window.addEventListener('load', ...)
: 监听window
对象的load
事件,确保在页面上所有的资源(包括图片、样式表等)都已加载完毕时触发。这里用于计算整个页面从开始加载到完全加载完成所需的时间。 -
document.querySelectorAll('.flex-item')
: 选择页面中所有带有.flex-item
类的元素,以便进一步检查它们的渲染状态。 -
checkElement
函数:
-
递归检查给定元素是否已被赋予非零的宽度和高度。如果条件满足,则认为该元素已经完成了基本的布局过程,并记录下此时与
startTime
之间的时间差。 -
使用
requestAnimationFrame
来优化递归调用,这样可以确保只有在浏览器准备重绘时才进行检查,提高效率并减少CPU占用。
document.addEventListener('DOMContentLoaded', measureRenderTime)
: 确保measureRenderTime
函数只在DOM完全加载之后执行。这保证了在尝试访问任何DOM元素之前,这些元素确实已经存在于文档中。