如何做前端性能优化

459 阅读6分钟

做前端性能优化,从思路上来说就是让页面渲染更快,更流畅,那么哪些环节会影响到页面渲染呢?我们从一道常见的前端面试题说起:从输入网址到页面展示的过程是怎样的呢?

一.浏览器渲染流程

主要分为两大部分,一个是资源加载与解析(首次渲染)一个是用户交互(运行)

1.资源加载与解析(首次渲染)

用一个流程图简单演示。

graph LR
输入URL -->  发起HTTP请求 --> 获取资源 --> 解析HTML:DOM树 --> 解析CSS:StyleSheets --> Layout树:回流 --> 生成图层:重绘 --> 合成位图 --> 显示

那么在第一部分我们可以做哪些性能优化呢?

  • HTTP部分

    1.1 DNS解析,一般我们在地址栏输入的URL都是域名,比如 baidu.com ,但在计算机的世界里是无法理解域名的,需要转换成IP地址进行通信,所有要进行DNS解析,也就是把域名解析成对应的IP地址。从这一点来说,需要转换的域名越少,需要进行DNS的时间就越少。所以可以适当减少资源放置的域名。

    1.2 缓存,(1)如果浏览器检测到HTTP有缓存且没有过期,则直接使用缓存资源。服务器端可以设置响应头cache-control: max-age设置资源的缓存时间。(2)提取公共代码,进行复用。

    1.3 TCP连接,一般浏览器会对同一域名的并行连接数有限制,例如chrome是6个,超过则需要排队。所以不同的资源尽量放不同域名下,增加并行TCP连接数,那么这里和DNS解析的优化策略是有些冲突的,需要做一个平衡。

    1.4 资源压缩,(1)浏览器和服务器可以约定资源的压缩方式,请求头设置accept-encoding:gzip, deflate, br;响应头设置content-encoding:gzip。上面表示浏览器支持gzip, deflate, br三种压缩方式,服务器告诉浏览器采用了gzip压缩。(2)通过构建工具,将前端代码进行压缩,删除空格等。

  • 渲染部分

    2.1 HTML解析,(1)文件解析是从上往下执行,如果将css文件放置在底部,在解析完DOM结构以后会再次进行css解析,也就引起了回流与重绘,所以应将css放置在header头部。(2)遇到script标签的时候会暂停html与css解析,执行javaScript,这就阻碍了用户看到首屏的时间,尽量将非必要javaScript代码放置到body底部,还有一种处理方式,可以给script标签加上defer或者async属性,则会对javascript做异步加载处理,不会阻塞html与css解析。

    2.2 CSS解析,css的书写顺序也很重要,如果先解析了颜色字体等属性,解析到定位属性的时候就需要重新进行布局计算,所以推荐按以下顺序书写css:

    • (1)定位属性:position,display,float,left,top,right,bottom,overflow,clear,z-index
    • (2)自身属性:width,height,padding,border,margin,background
    • (3)文字样式:font-family,font-size,font-style,font-weight,color
    • (4)文本属性:text-align,text-indent
    • (5)css3中新增属性:content,box-shadow,border-radius,transform……

    2.3 预渲染,与骨架屏配合,在接口请求返回之前,进行内容占位,减少用户焦虑。

    2.4 JS解析,多个script的标签会创建多个js解析环境,可以合并小的js逻辑代码,提高解析速度。

2.用户交互(运行)
  1. 操作DOM,操作DOM是对性能很大的消耗,会引起回流与重绘,可以缓存查询对象,将多次更新合并,例如Vue采用异步更新策略,将多次对DOM的操作一次性更新,提高性能。

  2. 动画,动画也是常用的功能,如果每一帧都改变元素位置,那么同样会引起回流与重绘。(1)css采用will-change,提前告知哪些属性会改变,例如opacity,transform会将元素分层,使用GPU对动画加速,将当前元素变形,进行位图合成。(2)js使用requestAnimationFrame替代定时器,将帧改变与显示器更新同步,避免丢帧与延迟。

二.浏览器原理

第二部分简单讲解一下浏览器原理,浏览器是多进程架构的。我们平常说JS是单线程的,那么具体是指什么意思呢?留着疑问我们往下看。

1.进程与线程
  • 进程是CPU进行资源分配的最小单位,启动一个程序的时候,操作系统会为该程序创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程,我们把这样的一个运行环境叫进程
  • 线程是CPU执行任务的最小调度,线程是依附于进程的,而进程中使用多线程并行处理能提升运算效率。
2.Chrome多进程架构

以Chrome为例,现代浏览器的多进程架构是由浏览器主进程GPU进程网络进程渲染进程插件进程构成。

  • 浏览器进程。主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
  • 渲染进程。核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
  • GPU 进程。其实,Chrome 刚开始发布的时候是没有 GPU 进程的。而 GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程。
  • 网络进程。主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
  • 插件进程。主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。

从进程角度再次复盘渲染流程

image.png

  • 首先,浏览器进程接收到用户输入的 URL 请求,浏览器进程便将该 URL 转发给网络进程。然后,在网络进程中发起真正的 URL 请求。
  • 接着网络进程接收到了响应头数据,便解析响应头数据,并将数据转发给浏览器进程。
  • 浏览器进程接收到网络进程的响应头数据之后,发送“提交导航 (CommitNavigation)”消息到渲染进程;
  • 渲染进程接收到“提交导航”的消息之后,便开始准备接收 HTML 数据,接收数据的方式是直接和网络进程建立数据管道;
  • 最后渲染进程会向浏览器进程“确认提交”,这是告诉浏览器进程:“已经准备好接受和解析页面数据了”。
  • 浏览器进程接收到渲染进程“提交文档”的消息之后,便开始移除之前旧的文档,然后更新浏览器进程中的页面状态。

那么现在解决我们一开始的疑问,为什么说JS是单线程的,是指JavaScript引擎是渲染进程中的一个线程。这也是为什么网络请求不会阻塞JS代码的原因。