Skip to content

Latest commit

 

History

History
229 lines (149 loc) · 10.7 KB

浏览器渲染原理与性能优化面试题.md

File metadata and controls

229 lines (149 loc) · 10.7 KB

01. 浏览器同步和异步加载资源

客户端从服务器获取到需要渲染的 HTML 页面后,会开启一个 GUI 渲染线程,自上而下的解析代码,最后绘制出页面。

自上而下解析代码的过程是同步的,但是加载代码里的资源有些是异步的:

  1. 加载 css 资源
  • 遇到 <style></style> 内嵌样式,同步 交给 GUI 渲染线程解析。
  • 遇到 外链样式,异步 开辟一个 http 网络请求线程请求资源,GUI 渲染线程不会等待请求的结果,直接继续向下解析渲染,GUI 渲染线程等待所有的同步操作都完成后,再来解析请求回来的资源。
  • 遇到 @import 导入的样式,同步 开辟一个 http 网路请求线程,但是在请求结果返回之前,GUI 渲染线程会被阻塞
  1. 加载 js 资源
  • script 标签请求 js 资源,默认都是 同步 的,GUI 渲染会阻塞等待请求回来的资源
  • async 属性,GUI 渲染线程不会阻塞,而是继续向下解析渲染,等资源请求回来后,马上解析请求回来的资源
  • defer 属性,GUI 渲染线程不会阻塞,而是继续向下解析渲染,等资源请求回来后,GUI 渲染线程不会马上解析请求回来的资源,而是等待所有的同步操作都完成后,再解析请求回来的资源

总结:async 有可能会改变资源解析的顺序(先请求的资源后返回结果)

  1. 加载 img 和音视频资源

异步 GUI 渲染线程会等待资源请求回来再向下渲染,所以是阻塞的

02. 页面的渲染流程

DOM 树 -> CSS 树 -> attachment -> layout -> layers -> paint

  1. DOM tree: 解析 HTML 生成 DOM 树(document)
  2. CSSOM tree: 解析 CSS 生成 CSS 树(document.stylesheets)
  3. Render tree: 将 DOM 树 和 CSS 树合并生成 Render 树(既包含结构也包含样式,display: none 的元素不包含)
  4. Layout: 计算每个元素渲染到页面的具体位置
  5. Layers: 通过布局树进行分层,生成图层树
  6. Painting: 根据每个图层的绘制步骤,绘制页面
  7. Display

03. 关键渲染路径

JS 触发视觉变化 -> 样式计算 -> 布局(重排/回流) -> 重绘 -> 合成

04. 重排(回流)和重绘的区别

  • 重排一定会导致重绘,重绘不一定会重排
  • 重排/回流:新增删除元素,修改元素的大小,改变元素的位置,获取元素的位置相关的信息,视口大小的变化
  • 重绘:元素样式的改变不影响元素在文档流中的位置(color, background-color, opacity, visibility, transform)

05. 如何减少回流和重绘

  • 读写分离,按需缓存布局信息,避免频繁读取
  • 脱离文档流
  • 合理利用特殊样式属性(如 transform: translateZ(0) 或者 will-change),将渲染层提升为合成层,开启 GPU 加速,提高页面性能
  • 尽量使用 css3 动画
  • 渲染时给图片增加固定宽高

06. 从输入 URL 到页面渲染完成发生了什么?

  1. 判断是关键词搜索还是站点的访问
  2. URL 解析
  3. 缓存检查
  4. DNS 解析
  5. TCP 三次握手建立连接通过,如果是 https 还要经历 ssl 协商
  6. 数据传输
  7. TCP 四次挥手
  8. 渲染页面

07. 前端性能优化

  1. 利用缓存(强缓存和协商缓存),数据采用 localStorage 缓存

现代的项目都是经 webpack 打包的,文件名都是带有 hash 的,可以使用强缓存,因为当文件内容变化后,打包出来的文件名也会变成新的,所以加载新的文件,自然旧的缓存不会产生新的影响

  1. CDN 服务器分地域分布

css、图片等不经常改变的资源可以放在 CDN 上,客户端用户根据位置从就近的 CDN 上获取

  1. DNS 优化

使用 dns-prefetch 预解析域名

  1. 减少数据传输的大小
  • 压缩资源(webpack 等)
  • 服务器开启 gzip(请求 gzip 资源)
  • 大数据量分批次处理(下拉刷新、分页等)
  1. 减少 http 请求次数
  • 资源合并(雪碧图等)
  • 小图片 base64 编码
  • 字体图标
  1. 改变资源加载的优先级
  • 懒加载
  • prefetch
  • preload
  1. 静态资源的优化
  • 图片选择合适的格式
  • HTML 语义化,尽量减少 html 的嵌套层级,减少使用 iframe
  • css 选择器减少层级,使用外链 css 可以利用缓存,减少 @import 的使用,阻塞渲染
  • 使用 async defer 异步加载 js
  1. 使用 HTTP2.0

性能优化 - 静态资源优化

  1. 图片优化

图片格式: - jpg 适合色彩丰富的照片、banner 图;不适合图形文字、图标(纹理边缘有锯齿感),不支持透明度 - png 适合纯色、透明、图标,支持半透明;不适合色彩丰富图片,因为无损存储会导致体积大 - gif 适合动画,可以动的图标;不支持半透明,不适合存储彩色图片 - webp 适合半透明图片,可以保证图片质量和较小体积;兼容性不好(谷歌推出的) - svg 比 jpg 体积更小,渲染成本过高,适合小且色彩单一的图标

图片优化: - 避免空 src 的图片 - 减小图片尺寸,节约用户流量 - img 标签设置 alt 属性,提升图片加载失败时的用户体验 - 原生的 loading: lazy 图片懒加载 - 不同环境下,加载不同尺寸和像素的图片 - 对于较大的图片可以考虑采用渐进式图片 - 采用 base64 减少图片请求 - 采用雪碧图

  1. HTML 优化
  • 语义化 HTML,代码简洁清晰,利于搜索引擎,便于团队开发
  • 提前声明字符编码,让浏览器快速确定如何渲染网页内容
  • 减少 HTML 嵌套层级,减少 DOM 节点数量
  • 删除多余的空格、空行、注释及无用的属性等
  • HTML 减少 iframe 使用
  • 避免使用 table 布局
  1. CSS 优化
  • 减少伪类选择器、减少样式层数、减少使用通配符
  • 避免使用 css 表达式
  • 删除空行、注释、减少无意义的单温
  • 使用外链 css,可以对 css 进行缓存
  • 添加媒体字段,只加载有效的 css 文件
  • 减少 @import 的使用
  1. JS 优化
  • 通过 async、defer 异步加载文件
  • 减少 DOM 操作,缓存访问过的元素
  • 使用 webworker 解决程序阻塞问题
  • IntersectionObserver
  • 虚拟列表
  • requestAnimationFrame、requestIdleCallback
  • 尽量避免使用 eval,消耗时间久
  • 使用事件委托,减少事件绑定个数(document.body 上做事件委托,需要使用passive:true选项来避免额外的问题)
  • 尽量使用 canvas 动画,css 动画
  1. 字体优化
  • FOUT(Flash Of Unstyled Text)等待一段时间,如果没加载完成,先显示默认。加载完成后在进行切换
  • FOIT(Flash Of Invisible Text)字体加载完毕后显示,加载超时降级系统字体(白屏)

为什么 css 文件要放在 head 里面,不要放在 body 底部?

浏览器在渲染的时候,会先解析 html,等待 head 里的 css 加载回来,然后去解析 css,然后渲染;如果 css 样式放在 body 底部,在样式之前的 dom 会渲染一次,然后等 body 底部的 css 样式加载完成后再来一次渲染(重绘)

为什么 js 文件要放在 body 底部呢?

js 代码会阻塞 html 解析和页面渲染(浏览器会等待 js 加载回来后,在开始继续渲染),但是 js 会等待它上面的 css 文件都加载完成再执行,以保证可以正确的操作元素的样式

08. 进程与线程

  1. 进程
  • 进程是 CPU 分配资源的最小单位
  • 在同一个时间内,单个 CPU 只能执行一个任务,只能运行一个进程
  • 如果有一个进程正在进行,其他进程就的暂停
  • CPU 使用了时间片轮转的算法来实现多进程的调度
  1. 线程
  • 线程是 CPU 调度的最小单位
  • 一个进程可以包括多个线程,这些线程共享该进程的资源

08. 浏览器主要有哪些进程

  • Browser 进程:主进程,负责浏览器界面显示,页面管理,其他子进程的创建和销毁等
  • Render 进程:渲染进程,用于控制和处理选项卡周玲的网站内容显示(每个 tab 页都有一个渲染进程)
  • GPU 进程:用于完成图像处理任务,同时还支持分解成多个进程进行处理(当 GPU 开启硬件加速时才会开启)
  • NPAPI 插件进程:管理浏览器中的各个插件
  • 网络进程:处理网络请求、文件访问等操作
  • Storage 进程
  • Audio 进程

Network、Storage、Audio 进程都是现代浏览器独立出来的

09. 浏览器为什么是多进程的?多进程的目的?

浏览器可以打开多个页签,如果浏览器是单进程的,某个页签的页面崩溃了,那么其他页面也就无法访问了

多进程的目的:

  • 职责分离,故障范围小(比如一个页面的崩溃不会影响到其他的页面)
  • 隔离性(每个页面之间是独立的进程,相互之间不受影响)
  • 性能

10. 浏览器进程有哪些线程?渲染进程有哪些线程?

浏览器进程的线程如下:

  • UI 线程:用于绘制浏览器的按钮和输入字段

现代浏览器已经将 Network、Storage 独立出新的进程了

渲染进程的线程如下:

  • GUI 渲染线程:渲染、布局和绘制页面
  • JS 引擎线程:解析执行 JS 脚本
  • 事件触发线程:当事件满足触发条件时,把事件放入到 JS 引擎任务队列中执行
  • 定时器触发线程:定时任务并不是由 JS 引擎线程计时的,而是由定时器触发线程来计时的,计时完毕后会通知事件触发线程

GUI 渲染线程和 JS 引擎线程是互斥的,同一时间两个线程最多只会有一个线程在工作

输入 URL,到浏览器渲染页面这个过程中,浏览器进程和渲染进程会进行很多的交互逻辑,最终页面才得以将页面内容渲染到页面上

11. gzip 压缩的原理?前端所有文件都需要 gzip 压缩吗?

gzip 压缩原理:

客户端请求头 Accept-Encoding 声明浏览器支持的压缩方式,服务端配置启用压缩,压缩的文件类型,压缩方式。当客户端请求到服务端的时候,服务器解析请求头,如果客户端支持 gzip 压缩,响应时对请求的资源进行压缩并返回给客户端,浏览器按照自己的方式解析,在 http 响应头,我们可以看到 Content-Encoding: gzip,这是指服务端使用了 gzip 压缩。

前端代码经 webpack 打包时,使用 compression-webpack-plugin 进行 gzip 压缩

哪些文件适合 gzip 压缩呢来优化性能呢?

html, css, js。简单来说,gzip 压缩是在一个文本文件中找出类似的字符串,并临时替换它们,是整个文件变小

gzip 压缩的缺点:

jpeg 这类文件用 gzip 压缩的不够好。客户端解析也占据了一些时间,但是随着硬件性能不断的提高,这些问题正在不断的弱化。