[TOC]
# 渲染引擎工作流程
如下图所示为渲染引擎的渲染流程示意图,其以HTML/JavaScript/CSS等文件作为输入,以可视化内容作为输出。

1. **Parsing HTML to Construct DOM Tree**
渲染引擎使用HTML解析器(调用XML解析器)解析HTML(XML)文档,将各个HTML(XML)元素逐个转化成DOM节点,从而生成DOM树。
同时,渲染引擎使用CSS解析器解析外部CSS文件以及HTML(XML)元素中的样式规则。元素中带有视觉指令的样式规则将用于下一步,以创建另一个树结构:渲染树。
2. **Render Tree construction**
渲染引擎使用第1步CSS解析器解析得到的样式规则,将其附着到DOM树上,从而构成渲染树。
渲染树包含多个带有视觉属性(如颜色和尺寸)的矩形。这些矩形的排列顺序就是它们将在屏幕上显示的顺序。
3. **Layout of Render Tree**
渲染树构建完毕之后,进入本阶段进行“布局”,也就是为每个节点分配一个应出现在屏幕上的确切坐标。
4. **Painting Render Tree**
渲染引擎将遍历渲染树,并调用显示后端将每个节点绘制出来。
<br>
<br>
# 渲染引擎组成模块
下图所示为渲染引擎工作流程中各个步骤所对应的模块,其中第1步和第2步涉及到多个模块,并且耦合程度较高。这样的设计会为了达到更好的用户体验,渲染引擎尽快将内容显示在屏幕上。它不必等到整个HTML文档解析完毕之后,就可以开始渲染树构建和布局设置。在不断接收和处理来自网络的其余内容的同时,渲染引擎会将部分内容解析并显示出来。

从图中可以看出,渲染引擎主要包含(或调用)的模块有:
* **HTML(XML)解析器**
解析HTML(XML)文档,主要作用是将HTML(XML)文档转换成DOM树。
* **CSS解析器**
将DOM中的各个元素对象进行计算,获取样式信息,用于渲染树的构建。
* **JavaScript解释器**
使用JavaScript可以修改网页的内容、CSS规则等。JavaScript解释器能够解释JavaScript代码,并通过DOM接口和CSSOM接口来修改网页内容、样式规则,从而改变渲染结果。
* **布局**
DOM创建之后,渲染引擎将其中的元素对象与样式规则进行结合,可以得到渲染树。布局则是针对渲染树,计算其各个元素的大小、位置等布局信息。
* **绘图**
使用图形库将布局计算后的渲染树绘制成可视化的图像结果。
<br>
<br>
# 为什么操作 DOM 慢
因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。
<br>
> 经典面试题:插入几万个 DOM,如何实现页面不卡顿?
对于这道题目来说,首先我们肯定不能一次性把几万个 DOM 全部插入,这样肯定会造成卡顿,所以解决问题的重点应该是如何分批次部分渲染 DOM。大部分人应该可以想到通过`requestAnimationFrame`的方式去循环的插入 DOM,其实还有种方式去解决这个问题:**虚拟滚动**(virtualized scroller)。
**这种技术的原理就是只渲染可视区域内的内容,非可见区域的那就完全不渲染了,当用户在滚动的时候就实时去替换渲染的内容。**

<br>
即使列表很长,但是渲染的 DOM 元素永远只有那么几个,当我们滚动页面的时候就会实时去更新 DOM,这个技术就能顺利解决这道经典面试题。如果你想了解更多的内容可以了解下这个[react-virtualized](https://link.juejin.im/?target=https%3A%2F%2Fgithub.com%2Fbvaughn%2Freact-virtualized)。
<br>
# 资源异步同步加载
首先渲染的前提是生成渲染树,所以 HTML 和 CSS 肯定会阻塞渲染。如果你想渲染的越快,你越应该降低一开始需要渲染的文件**大小**,并且**扁平层级,优化选择器**。
<br>
然后当浏览器在解析到`script`标签时,会暂停构建 DOM,完成后才会从暂停的地方重新开始。也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将`script`标签放在`body`标签底部的原因。
<br>
当然在当下,并不是说`script`标签必须放在底部,因为你可以给`script`标签添加`defer`或者`async`属性。
<br>
## 解析过程

## 普通script
文档解析的过程中,如果遇到`script`脚本,就会停止页面的解析进行下载(但是Chrome会做一个优化,如果遇到`script`脚本,会快速的查看后边有没有需要下载其他资源的,如果有的话,会先下载那些资源,然后再进行下载`script`所对应的资源,这样能够节省一部分下载的时间)。
资源的下载是在解析过程中进行的,虽说`script1`脚本会很快的加载完毕,但是他前边的`script2`并没有加载&执行,所以他只能处于一个挂起的状态,等待`script2`执行完毕后再执行。
当这两个脚本都执行完毕后,才会继续解析页面。

<br>
## defer
* 如果`script`标签设置了`defer`性,则浏览器会异步的下载该文件并且不会影响到后续`DOM`的渲染;
* 如果有多个设置了`defer`的`script`标签存在,则会按照顺序执行所有的`script`;
* `defer`脚本会在文档渲染完毕后,`DOMContentLoaded`事件调用前执行。
* 文档解析时,遇到设置了`defer`的脚本,就会在后台进行下载,但是并不会阻止文档的渲染,当页面解析&渲染完毕后。
* 会等到所有的`defer`脚本加载完毕并按照顺序执行,执行完毕后会触发`DOMContentLoaded`事件。

<br>
使用场景
如果你的脚本代码依赖于页面中的`DOM`元素(文档是否解析完毕),或者被其他脚本文件依赖。
**例:**
1. 评论框
2. 代码语法高亮
3. `polyfill.js`
<br>
## async
* `async`的设置,会使得`script`脚本异步的加载并在允许的情况下执行
* `async`的执行,并不会按着`script`在页面中的顺序来执行,而是谁先加载完谁执行。
* `async`脚本会在加载完毕后执行。
* `async`脚本的加载不计入`DOMContentLoaded`事件统计,也就是说下图两种情况都是有可能发生的


<br>
使用场景
如果你的脚本并不关心页面中的`DOM`元素(文档是否解析完毕),并且也不会产生其他脚本需要的数据。
**例:**
1. 百度统计
> 如果不太能确定的话,用`defer`总是会比`async`稳定。
<br>
# 参考资料
[现代浏览器工作原理(一)](http://chuquan.me/2018/01/21/browser-architecture-overview/)
前端面试之道 - 掘金手册
[浅谈script标签中的async和defer](https://www.cnblogs.com/jiasm/p/7683930.html)
- 第一部分 HTML
- meta
- meta标签
- HTML5
- 2.1 语义
- 2.2 通信
- 2.3 离线&存储
- 2.4 多媒体
- 2.5 3D,图像&效果
- 2.6 性能&集成
- 2.7 设备访问
- SEO
- Canvas
- 压缩图片
- 制作圆角矩形
- 全局属性
- 第二部分 CSS
- CSS原理
- 层叠上下文(stacking context)
- 外边距合并
- 块状格式化上下文(BFC)
- 盒模型
- important
- 样式继承
- 层叠
- 属性值处理流程
- 分辨率
- 视口
- CSS API
- grid(未完成)
- flex
- 选择器
- 3D
- Matrix
- AT规则
- line-height 和 vertical-align
- CSS技术
- 居中
- 响应式布局
- 兼容性
- 移动端适配方案
- CSS应用
- CSS Modules(未完成)
- 分层
- 面向对象CSS(未完成)
- 布局
- 三列布局
- 单列等宽,其他多列自适应均匀
- 多列等高
- 圣杯布局
- 双飞翼布局
- 瀑布流
- 1px问题
- 适配iPhoneX
- 横屏适配
- 图片模糊问题
- stylelint
- 第三部分 JavaScript
- JavaScript原理
- 内存空间
- 作用域
- 执行上下文栈
- 变量对象
- 作用域链
- this
- 类型转换
- 闭包(未完成)
- 原型、面向对象
- class和extend
- 继承
- new
- DOM
- Event Loop
- 垃圾回收机制
- 内存泄漏
- 数值存储
- 连等赋值
- 基本类型
- 堆栈溢出
- JavaScriptAPI
- document.referrer
- Promise(未完成)
- Object.create
- 遍历对象属性
- 宽度、高度
- performance
- 位运算
- tostring( ) 与 valueOf( )方法
- JavaScript技术
- 错误
- 异常处理
- 存储
- Cookie与Session
- ES6(未完成)
- Babel转码
- let和const命令
- 变量的解构赋值
- 字符串的扩展
- 正则的扩展
- 数值的扩展
- 数组的扩展
- 函数的扩展
- 对象的扩展
- Symbol
- Set 和 Map 数据结构
- proxy
- Reflect
- module
- AJAX
- ES5
- 严格模式
- JSON
- 数组方法
- 对象方法
- 函数方法
- 服务端推送(未完成)
- JavaScript应用
- 复杂判断
- 3D 全景图
- 重载
- 上传(未完成)
- 上传方式
- 文件格式
- 渲染大量数据
- 图片裁剪
- 斐波那契数列
- 编码
- 数组去重
- 浅拷贝、深拷贝
- instanceof
- 模拟 new
- 防抖
- 节流
- 数组扁平化
- sleep函数
- 模拟bind
- 柯里化
- 零碎知识点
- 第四部分 进阶
- 计算机原理
- 数据结构(未完成)
- 算法(未完成)
- 排序算法
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
- 搜索算法
- 动态规划
- 二叉树
- 浏览器
- 浏览器结构
- 浏览器工作原理
- HTML解析
- CSS解析
- 渲染树构建
- 布局(Layout)
- 渲染
- 浏览器输入 URL 后发生了什么
- 跨域
- 缓存机制
- reflow(回流)和repaint(重绘)
- 渲染层合并
- 编译(未完成)
- Babel
- 设计模式(未完成)
- 函数式编程(未完成)
- 正则表达式(未完成)
- 性能
- 性能分析
- 性能指标
- 首屏加载
- 优化
- 浏览器层面
- HTTP层面
- 代码层面
- 构建层面
- 移动端首屏优化
- 服务器层面
- bigpipe
- 构建工具
- Gulp
- webpack
- Webpack概念
- Webpack工具
- Webpack优化
- Webpack原理
- 实现loader
- 实现plugin
- tapable
- Webpack打包后代码
- rollup.js
- parcel
- 模块化
- ESM
- 安全
- XSS
- CSRF
- 点击劫持
- 中间人攻击
- 密码存储
- 测试(未完成)
- 单元测试
- E2E测试
- 框架测试
- 样式回归测试
- 异步测试
- 自动化测试
- PWA
- PWA官网
- web app manifest
- service worker
- app install banners
- 调试PWA
- PWA教程
- 框架
- MVVM原理
- Vue
- Vue 饿了么整理
- 样式
- 技巧
- Vue音乐播放器
- Vue源码
- Virtual Dom
- computed原理
- 数组绑定原理
- 双向绑定
- nextTick
- keep-alive
- 导航守卫
- 组件通信
- React
- Diff 算法
- Fiber 原理
- batchUpdate
- React 生命周期
- Redux
- 动画(未完成)
- 异常监控、收集(未完成)
- 数据采集
- Sentry
- 贝塞尔曲线
- 视频
- 服务端渲染
- 服务端渲染的利与弊
- Vue SSR
- React SSR
- 客户端
- 离线包
- 第五部分 网络
- 五层协议
- TCP
- UDP
- HTTP
- 方法
- 首部
- 状态码
- 持久连接
- TLS
- content-type
- Redirect
- CSP
- 请求流程
- HTTP/2 及 HTTP/3
- CDN
- DNS
- HTTPDNS
- 第六部分 服务端
- Linux
- Linux命令
- 权限
- XAMPP
- Node.js
- 安装
- Node模块化
- 设置环境变量
- Node的event loop
- 进程
- 全局对象
- 异步IO与事件驱动
- 文件系统
- Node错误处理
- koa
- koa-compose
- koa-router
- Nginx
- Nginx配置文件
- 代理服务
- 负载均衡
- 获取用户IP
- 解决跨域
- 适配PC与移动环境
- 简单的访问限制
- 页面内容修改
- 图片处理
- 合并请求
- PM2
- MongoDB
- MySQL
- 常用MySql命令
- 自动化(未完成)
- docker
- 创建CLI
- 持续集成
- 持续交付
- 持续部署
- Jenkins
- 部署与发布
- 远程登录服务器
- 增强服务器安全等级
- 搭建 Nodejs 生产环境
- 配置 Nginx 实现反向代理
- 管理域名解析
- 配置 PM2 一键部署
- 发布上线
- 部署HTTPS
- Node 应用
- 爬虫(未完成)
- 例子
- 反爬虫
- 中间件
- body-parser
- connect-redis
- cookie-parser
- cors
- csurf
- express-session
- helmet
- ioredis
- log4js(未完成)
- uuid
- errorhandler
- nodeclub源码
- app.js
- config.js
- 消息队列
- RPC
- 性能优化
- 第七部分 总结
- Web服务器
- 目录结构
- 依赖
- 功能
- 代码片段
- 整理
- 知识清单、博客
- 项目、组件、库
- Node代码
- 面试必考
- 91算法
- 第八部分 工作代码总结
- 样式代码
- 框架代码
- 组件代码
- 功能代码
- 通用代码