流程总览
解析流程是浏览器渲染步骤中的第一个步骤,主要完成三种类型文件的解析
- HTML解析
- CSS解析
- JavaScript解析
浏览器解析入口一定是一个html页面,html也是解析流程的入口。css和js相关文件或内容嵌套在html的多个地方,当解析到它们时,会立即停止html解析,进入css或者js的解析流程,解析完成之后再回到html解析流程。解析完成html文件最后一个标签,整个解析即结束。
- 遇到以下标签或属性时,进入css解析流程
<link rel="stylesheet" href="cssUrl">
,发起网络请求href对应资源<style>{css源码}</style>
<div style="width: 50%..."></div>
- 遇到script标签时,进入js解析流程
<script src="jsUrl"></script>
,发起网络请求src对应资源<script>{js源码}</script>
HTML解析
执行时机:浏览器进入解析流程即同步开启 HTML解析主要任务是:解析html文本,生成html元素节点,识别html嵌套结构并构建DOM树
- 令牌化Token【词法分析】
- html解析器会依次读取html文本,依照html语法规则生成Token(涉及到编译原理知识)
- Token有6种类型
// 开始标签令牌示例
const startTagToken = {
type: "startTag",
tagName: "div",
attributes: [
{ name: "class", value: "container" }
]
};
// 结束标签令牌示例
const endTagToken = {
type: "endTag",
tagName: "div"
};
// 自闭合标签令牌示例
const selfClosingTagToken = {
type: "selfClosingTag",
tagName: "img",
attributes: [
{ name: "src", value: "image.jpg" },
{ name: "alt", value: "An image" }
]
};
// 文本令牌示例
const textToken = {
type: "text",
content: "Hello, World!"
};
// 注释令牌示例
const commentToken = {
type: "comment",
content: "This is a comment"
};
// 文档类型令牌示例
const doctypeToken = {
type: "doctype",
name: "html"
};
- Tokens结构
// html
<div class="container">
<p>Hello, World!</p>
</div>
// 令牌Tokens
[`<div class="container">`, `<p>`, `Hello, World!`, `</p>`, `</div>`]
[
{
type: "startTag",
tagName: "div",
attributes: [
{ name: "class", value: "container" },
],
},
{
type: "startTag",
tagName: "p",
attributes: [],
},
{
type: "text",
content: "Hello, World!",
},
{
type: "endTag",
tagName: "p",,
},
{
type: "endTag",
tagName: "div",
},
]
- 生成html元素节点【语法分析】
遍历Tokens数据
- type为startTag时,创建tagName对应的html元素节点并通过parentElement.appendChild挂载,最后将“parentElement”改为当前元素。
- type为endTag时,“parentElement”改为当前节点的父元素。
- type为selfClosingTag时,创建tagName对应的html元素节点并通过parentElement.appendChild挂载。
- type为text时,创建一个文本元素节点并通过parentElement.appendChild挂载。
- type为comment时,创建一个注释元素节点并通过parentElement.appendChild挂载。
- type为doctype时,html解析器会确定文本类型和解析模式(标准模式、混杂模式)。
- 构建DOM树
DOM以document为根节点(默认parentElement为document),DOM是在生成html元素节点过程中持续完善,当一个元素创建后,立即挂载到DOM当中。
// DOM树结构
{
nodeName: "DIV",
nodeType: 1, // ELEMENT_NODE
tagName: "DIV",
attributes: [
{ name: "class", value: "container" }
],
childNodes: [
{
nodeName: "P",
nodeType: 1,
tagName: "P",
attributes: [],
childNodes: [
{
nodeName: "#text",
nodeType: 3, // TEXT_NODE
nodeValue: "Hello, World!",
},
],
}
]
}
CSS解析
执行时机:穿插于html解析 CSS解析主要任务是:加载css资源,解析css文本,构建CSSOM树,样式规则计算
- 加载css资源
- link标签,发送请求获取css文件
- style标签,提取style标签中的css内容
- 内联样式,提取元素中style属性内容
- 令牌化Token【词法分析】
- css解析器依次读取css文本,根据css标准语法,提取各种类型Token(涉及到编译原理知识)
// css
div {
width: "90%";
background-color: #ffffff;
}
// 令牌Tokens
[
{
type: "selector",
value: "div",
},
{
type: "property",
value: "width",
},
{
type: "value",
value: "90%",
},
{
type: "property",
value: "background-color",
},
{
type: "value",
value: "#ffffff",
},
]
- 构建CSSOM树
遍历Tokens,生成CSSOM树
const cssom = {
rules: [
{
selectorText: "div",
style: {
width: "90%",
backgroundColor: "#ffffff",
},
},
],
}
- 样式规则计算
- 规则优先级
Cascade Order:
1. Author Styles (developer-defined)
2. User Styles (user-defined)
3. User Agent (browser default)
Priority:
1. !important
2. Inline styles
3. ID selectors
4. Class selectors
5. Element selectors
6. Universal selector (*)
- 计算样例
// html
<div class="container">
<p>Hello, World!</p>
</div>
<style>
div {
width: "90%";
background-color: "#ffffff";
}
div {
background-color: "#010101";
}
.container {
width: "80%";
color: "#000000";
}
</style>
以上述代码结构为例,div元素同时被style中的div和.container所命中,其中width属性冲突,.container为class属性,优先级高于元素选择器div,width最终命中width: 80%;其中background-color被两个div选择器选中,两个选择器优先级相同,按照申明先后顺序,后声明的覆盖之前。 故div元素最终样式为:
{
width: "80%"; // .container > div
background-color: "#010101"; // 后div > 前div
color: "#000000"; // .container > div
}
JavaScript解析
执行时机:穿插于html解析 JS解析主要任务是:加载js资源,解析js文本,创建AST,解析和优化代码
- 加载js资源
<script src="srcUrl"></script>
,发送网络请求,获取js资源文件<script>{ js源码 }</script>
,读取源码数据
- 令牌化Token【词法分析】 根据js标准语法,解析代码结构
// js
function greet(name) {
console.log('Hello, ' + name + '!');
}
greet('World');
// Tokens(大概解析状态)
[function, greet, (, name, ), {, console.log, (, 'Hello, ', +, name, +, '!', ), ;, ...]
- 创建AST
遍历Tokens,根据语法创建结构树
// js
function greet(name) {
console.log('Hello, ' + name + '!');
}
greet('World');
// AST
{
{
id: { name: 'greet' },
params: { name: 'name' },
body: {
callee: {
object: { name: 'console', property: 'log' },
},
arguments: { left: { value: 'Hello, ' }, ... }
},
}: FunctionDeclaration,
}
- 创建运行环境和资源
根据代码结构,创建入口模块执行上下文,初始化作用域链地址等(仅创建入口模块的资源,执行过程中可能会进入不同模块,会实时引入)
上述HTML、CSS、JavaScript解析完成之后,将进入“构建渲染树阶段”