浏览器渲染原理-解析阶段

652 阅读5分钟

流程总览

解析流程是浏览器渲染步骤中的第一个步骤,主要完成三种类型文件的解析

  • 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>

image.png

HTML解析

执行时机:浏览器进入解析流程即同步开启 HTML解析主要任务是:解析html文本,生成html元素节点,识别html嵌套结构并构建DOM树

  1. 令牌化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",
    },
]
  1. 生成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解析器会确定文本类型和解析模式(标准模式、混杂模式)。
  1. 构建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树,样式规则计算

  1. 加载css资源
  • link标签,发送请求获取css文件
  • style标签,提取style标签中的css内容
  • 内联样式,提取元素中style属性内容
  1. 令牌化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",
  },
]
  1. 构建CSSOM树

遍历Tokens,生成CSSOM树

const cssom = {
  rules: [
    {
      selectorText: "div",
      style: {
        width: "90%",
        backgroundColor: "#ffffff",
      },
    },
  ],
}
  1. 样式规则计算
  • 规则优先级
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,解析和优化代码

  1. 加载js资源
  • <script src="srcUrl"></script>,发送网络请求,获取js资源文件
  • <script>{ js源码 }</script>,读取源码数据
  1. 令牌化Token【词法分析】 根据js标准语法,解析代码结构
// js
function greet(name) {
  console.log('Hello, ' + name + '!');
}
greet('World');

// Tokens(大概解析状态)
[function, greet, (, name, ), {, console.log, (, 'Hello, ', +, name, +, '!', ), ;, ...]
  1. 创建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,
}
  1. 创建运行环境和资源

根据代码结构,创建入口模块执行上下文,初始化作用域链地址等(仅创建入口模块的资源,执行过程中可能会进入不同模块,会实时引入)


上述HTML、CSS、JavaScript解析完成之后,将进入“构建渲染树阶段”

浏览器渲染原理-构建阶段