Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术随笔

    • git教程
    • 前端模块化:CommonJS,AMD,CMD,ES6
    • 如何动态加载本地图片
    • GitHub自动部署脚本
    • React生命周期详解
    • 将父组件的props作为组件的初始state的几种方案
    • webpack基础入门
    • StackOver记录:如何在function中使用redux
    • React中使用onClick时的三种性能优化
    • 思考React状态管理
    • forEach/for..in/of
    • styled-components使用记录
    • 深入理解forEach与forof
    • Node技术架构
    • Redux、Mobx状态管理杂谈
    • JS中的类型检测
    • 如何判断图片是否在可视区域
    • 如何使用图片的懒加载操作
    • JavaScript模块化:从CommonJS到AMD
    • git查看:修改用户名、密码
    • 保持数据Immutable的几种写法
    • 如何实现一个Promise.all
    • resolve(Promise对象)会多出两个微任务
    • todo_基于Mocha的测试驱动开发
    • 浏览器页面渲染机制
      • 0.前言
      • 1.HTML的解析过程
      • 2.HTML解析被JS阻塞的原因
      • 3.async与defer的区别
      • 4.如何区分prefetch与preload
      • 5.页面渲染机制
      • 6.如何判定 reflow 和 repaint
      • 7. VSCode 插件
      • 8.总结
    • todo-响应式编程之数据劫持
    • todo-immer库的介绍
    • React-Hooks 基础原理解析
    • JS基本功
    • 函数闭包与this指针
    • 收集 DUMI 配置过程中遇到的问题
    • typescript 类型工具
    • 统一接口的导入导出
    • 网络安全:如何预防 XSS 攻击
    • 浏览器页面渲染机制【2023】
    • 跨域资源请求
    • 浏览器事件循环机制
    • 如何理解浏览器的 user agent 用户代理的含义?
  • 转载文章

  • Mac相关

  • 前端学习周报

  • 包管理工具

  • 技术随笔
  • 技术随笔
wangjiasheng
2022-02-24
目录

浏览器页面渲染机制

# 0.前言

本篇博客主要针对的是Web性能优化的基础理论知识,HTML资源是以何种方式何种顺序被加载到浏览器中的。

# 1.HTML的解析过程

假设现在有一个index.html资源:

<head>
	<link rel="stylesheet" type="text/css" href="./*.css" />
</head>
<body>
  <h1>你好</h1>
  <!-- 加载外部资源 -->
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js"></script>    
	<!-- 边下载边解析 -->
  <script type="text/javascript">
		document.write("Hello World!")
	</script>
  <div>更多内容...</div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13

HTML 解析过程如下:

image-20220224230257143
  • 边下载边解析HTML,以下均是HTML的解析过程:

    • 解析到:<link rel="stylesheet" type="text/css" href="./*.css" />

      于是开始去边下载边解析css资源

      css的解析和下载并不会阻塞HTML的解析过程。

    • 解析到:<h1>你好</h1>,开始构建DOM tree

    • 解析到:<script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js"></script>

      • 开始下载cdn资源,下载后执行javascript。

      🚨注意:此时javascript的下载和执行会阻塞HTML的解析

    • 解析到:<div>更多内容...</div>,此时解析结束。

# 2.HTML解析被JS阻塞的原因

以上HTML的解析过程会被两个行为阻塞:

  1. JS的下载过程:

    在早期浏览器中并不会处理此类阻塞,原因是:浏览器偷懒了,性价比不高。整个HTML解析过程可能只有1ms,而下载过程需要100ms,对性能的提升不大。

  2. JS的执行过程:

    原因是可能执行过程中遇到以下代码:

    <script type="text/javascript">
      document.write("Hello World!")
    </script>
    
    1
    2
    3

    即,JS的操作可能会修改DOM树的构建,如果HTML不能JS执行结束,就直接解析出DOM树,会存在DOM树构建失败。

# 3.async与defer的区别

通过设置async和defer属性可以将JS的下载,独立出HTML的解析,并且对JS的执行顺序也可以控制。

对于script可以设置如下标签:

<script></script>
<script defer></script>
<script async></script>
<script type="module"></script>
<script type="module" async></script>
1
2
3
4
5

下图为不同script属性值下的执行流程图:

其中,绿线代表HTML解析过程,蓝线代表JS下载,红线代表JS执行

从上图中可以得知:async和defer作用都是不让JS下载阻塞HTML执行。

两者的区别在于:下载后JS的执行顺序

  • async:这个属性只有一个原则,即一旦下载结束就立即执行js文件。

    上图中存在一个问题,看上去async会阻塞HTML解析,但是实际上fetch的下载要远远长于HTML的解析流程。

  • defer:推荐使用,因为 async 并不能保证 js 代码的执行顺序,执行顺序取决于网络下载情况,此时JS执行顺序会比较松散,而 defer 的执行顺序,取决于定义代码的位置。并且在 HTML 解析后执行。

  • module:过程基本等同于defer,衍生出的其余蓝线是包依赖的modules。

  • module+async:依赖结束后,直接执行。

# 4.如何区分prefetch与preload

这两个标签的作用是:让浏览器提前加载指定资源(与defer等字段不同的是:加载后并不执行),在需要执行的时候再执行。优点是:

  1. 将加载和执行分离开,不会阻塞HTML渲染和document的onload事件。
  2. 提前加载指定资源,不再出现依赖的font字体隔了一段时间才刷出。

使用方式:

<!-- 使用 link 标签静态标记需要预加载的资源 -->
<link rel="preload" href="/path/to/style.css" as="style">

<!-- 或使用脚本动态创建一个 link 标签后插入到 head 头部 -->
<script>
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'style';
link.href = '/path/to/style.css';
document.head.appendChild(link);
</script>
1
2
3
4
5
6
7
8
9
10
11

preload 和 prefetch的应用场景:

  1. preload 是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源。

  2. prefetch是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源。

    如,某一场景下,页面加载后会初始化首屏组件,当用户滚动页面时,会拉取第二屏的组件,若能预测用户行为,则可以 prefetch 下一屏的组件。

由此引出另一话题,资源的加载是存在优先级的,具体的请看:蚂蚁金服数据体验技术 (opens new window)出的文章。

# 5.页面渲染机制

以下内容来源于:Web Fundamentals (opens new window)

  1. HTML结构如下:
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
  1. 第1步,根据HTML文件,边下载边构建 DOM 树。(且会被js下载和执行阻塞)

    Chrome-dev-tools 流程图如下:此阶段为parse HTML

    在控制台输入

    document
    console.dir(document)
    
    1
    2
  2. 第2步,边下载边构建 CSSOM 树,css文件和CSSOM树如下图所示:

    body { font-size: 16px }
    p { font-weight: bold }
    span { color: red }
    p span { display: none }
    img { float: right }
    
    1
    2
    3
    4
    5

    可以发现,font-size有传导的作用

  3. 根据DOM和CSSOM生成 render tree 渲染树计算(Recalculate Style 阶段)

    注意:这里渲染树只会计算所有在页面上可见的 DOM 内容,以及每个node上的CSSOM样式。

  4. 以上结束后,触发 DOMContentLoaded 事件,说明render tree已经准备就绪,可以开始渲染。

  5. 页面渲染,即如何将 render tree转化为屏幕上的真实pixels

    三个阶段:layout、paint、composite

    1. 有了渲染树后,进入布局阶段(layout)

      布局阶段:会计算相对于视口的位置,大小和尺寸。

    2. 布局完成后,浏览器会立即发出“Paint”事件,将渲染树转换成屏幕上的像素。

    3. 图层树计算,进入composite阶段。

    对应的Chrome-dev-tools显示有:Try it (opens new window)

    下面的结果是错的(忘记关插件了.......)

# 6.如何判定 reflow 和 repaint

对性能影响最大的是页面内容的渲染,大部分我们只考虑 Layout阶段,和 Paint 阶段。

Layout 阶段的修改:reflow回流。

Paint 阶段的修改:repaint重绘。

而Composite阶段,只要页面被修改了则必定会触发,一般不会考虑此阶段的性能优化。

这里的优化方案如下:

  1. 通过查阅 csstrigger网站 (opens new window),查阅不同的 css 样式,会触发的Layout、Paint、Composite的影响。

    浏览器内核 渲染引擎
    IE Trident
    Firefox Gecko
    Safari Webkit
    Chrome Blink(WebKit 的分支)
    Opera 原为 Presto,现为 Blink

    这里还有一个需要关注:Change from default 。上面介绍浏览器的渲染机制的时候其实忽略了一点就是,其实浏览器内部已经存在一套默认的样式了,我们的CSS文件只是对原有样式的一层覆盖和修改。

  2. 选择对页面渲染影响较小的 css 样式,如:将 left 样式改为transform样式。

    transform:

    left:

    在 Chrome 和 Firefox 中,修改以上属性可以提高页面的渲染性能。

# 7. VSCode 插件

推荐:css-triggers VSCode 插件,插件下载地址 (opens new window)

# 8.总结

Webkit 主流程,已经可以很好的概括本篇博客的内容了。

编辑 (opens new window)
上次更新: 2023/09/17, 22:09:00
todo_基于Mocha的测试驱动开发
todo-响应式编程之数据劫持

← todo_基于Mocha的测试驱动开发 todo-响应式编程之数据劫持→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式