前端优化方案整理

148 阅读13分钟

一、关键渲染路径优化

@1 尽可能使用HTTP2.0

@2 保持TCP通道的长连接 Connection:keep-alive

@3 开启服务器端的GZIP压缩:「让每一次返回的资源大小压缩40%+」

@4 合理使用缓存方案:对不经常更新的数据做数据缓存

@5 对于静态资源文件设置强缓存和协商缓存:目的是保证第二次及以后加载页面更快

关于html/CSS的优化

@6 减少HTML嵌套的层级、使用语义化标签

@7 link导入样式放在HEAD中:让GUI渲染DOM TREE的同时,也去请求CSS资源,这样等到DOM TREE完成,可能CSS资源已经拿回来了

@8 script标签尽可能放到最后: 对于script标签来讲,尽可能放在页面末尾导入,如果非要放在前面导入,需要加async/defer,避免对GUI阻塞

@9 合理使用css样式引入方式: 如果样式资源比较少,直接内嵌式即可,减少一次HTTP请求;如果内容多,则合并到一个css中,使用外链式link导入;坚决不用@import导入式,因为他会阻塞GUI渲染!!

@10 CSS选择器前缀不要过长「CSS选择器的渲染方向:右->左」

@11 避免使用CSS的表达式{expression}

@12 合理使用图片base64: 需要基于webpack自己编译BASE64「不要自己手动写BASE64,因为代码太恶了」零散的小图片一般可以base64,如果这个图片很大,并且还很重要(不能延迟加载),如果想尽各种办法加载还是慢,不妨使用BASE64

@13 不同的资源分服务器部署:将原本放在一个服务器上的项目资源,分给多个服务器:web服务器、数据服务器、图片/音视频服务器、第三方服务器…

  • 优势:资源合理利用、减轻服务器压力、提高服务器处理能力、提高HTTP请求的并发数(每个服务器HTTP请求并发上限是5-7个)
  • 弊端:增加了DNS解析的次数,所以在这个基础上,我们使用DNS Prefetch预解析

DNS Prefetch的原理:利用link的异步性,让GUI渲染的同时,也去预先解析DNS(dns-prefetch),后面再获取资源的时候,直接把DNS缓存的信息拿出来用即可…

@14 减少因操作DOM而产生的重排和重绘:

放弃直接操作DOM:我们主攻操作数据,把操作DOM的事情交给vue/react框架来完成「框架内部做了很多操作DOM的优化」:虚拟DOM->DOM DIFF->渲染差异内容

  • 读写分离,利用浏览器的渲染队列机制:我们操作DOM的样式进行“「集中修改样式」”
  • 动态创建多个DOM节点时:我们基于文档碎片或者模板字符串,实现批量增加
  • 基于transform修改元素的样式:不会引发重排
  • 尽量操作脱离普通文档流的节点:这样节点位置或大小改变,只会把这一层中的节点重新Layout,虽然也引发了重排,但是总比所有节点都重新计算位置强…

二、页面打开速度优化

@1 图片懒加载: 第一次渲染页面不去加载真实图片(页面中基于默认图占位):减少了HTTP请求次数、不占用HTTP并发资源、第一次加载页面也无需渲染图片… 让页面第一次加载更快

@2 音视频资源一定要做延迟加载和播放

@3 骨架屏: 服务器骨架屏(SSR渲染):页面首屏需要展示的结构、样式、数据等都由服务器处理好,第一次加载页面,只要获取到内容,直接渲染即可(真实数据也有了) -> 前提服务器抗压能力需要好

  • 前端骨架屏:渲染之前的Loading效果;在真实内容没有渲染出来之前,先把架子搭起来,用一些灰色的框框占位,给用户正在加载的友好效果…

@4 减少HTTP的请求次数和大小:「因为HTTP的并发性、TCP的三握四挥、网络通道可能会被阻塞等众多原因,决定了HTTP请求次数越少越好」

  • CSS和JS资源各合并为一个:「如果一个文件过大,第一次加载页面不需要这么多东西,我们也可以切割成多个,但是第一次只加载一个必须用到的,其余的都动态异步加载」
  • 使用CSS Sprite(精灵图)技术:,多张图片合并为一个
  • 文件要压缩:图片资源在保证清晰度的前提下,尽可能压缩
  • 使用字体图标/SVG(矢量图)代替位图(jpg/png/gif…)

@5 做数据的分页加载、异步加载、下拉加载…

三、运行时代码优化

@1 事件委托: 「优势:减少堆栈内存的开辟、可以给动态创建的元素做事件绑定…提高了整体性能」

@2 合理使用闭包:「闭包会产生不释放的栈内存」

@3 避免内存泄漏: -> javascript高级程序设计第三版

@4 禁止出现死递归:(因为会导致栈溢出)、禁止出现死循环(因为会阻塞JS引擎线程的渲染)

@5 减少cookie的使用:

  • 「因为每一次向服务器发送请求,都会在请求头中把cookie传递给服务器,不论服务器是否想要,如果本地cookie存储信息多,则每次传输都会携带一些没必要的内容…」

@6 对于一些操作使用函数的节流和防抖

@7 动画处理的原则:能用CSS搞定的不用JS,能用requestAnimationFrame搞定的不用定时器,如果最后定时器动画都搞不定的,换需求…

四、webpack层面优化

1、Webpack 对图片进行压缩

在 vue 项目中除了可以在 webpack.base.conf.js 中 url-loader 中设置 limit 大小来对图片处理,对小于 limit 的图片转化为 base64 格式,其余的不做操作。所以对有些较大的图片资源,在请求资源的时候,加载会很慢,我们可以用 image-webpack-loader来压缩图片

2、减少 ES6 转为 ES5 的冗余代码

Babel 插件会在将 ES6 代码转换成 ES5 代码时会注入一些辅助函数,在默认情况下, Babel 会在每个输出文件中内嵌这些依赖的辅助函数代码,如果多个源代码文件都依赖这些辅助函数,那么这些辅助函数的代码将会出现很多次,造成代码冗余。为了不让这些辅助函数的代码重复出现,可以在依赖它们时通过require('babel-runtime/helpers/createClass') 的方式导入,这样就能做到只让它们出现一次。 babel-plugin-transform-runtime 插件就是用来实现这个作用的,将相关辅助函数进行替换成导入语句,从而减小 babel 编译出来的代码的文件大小。

3、提取公共代码 如果项目中没有去将每个页面的第三方库和公共模块提取出来,则项目会存在以下问题:

  • 相同的资源被重复加载,浪费用户的流量和服务器的成本。
  • 每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。

所以我们需要将多个页面的公共代码抽离成单独的文件,来优化以上问题 。Webpack 内置了专门用于提取多个Chunk 中的公共部分的插件 CommonsChunkPlugin

五、vue/react层面优化

1、路由懒加载

vue是单页面应用,而我们的网站通常又是有多个页面组成,所以会引入很多路由,如果统一都在首屏加载,那么经过webpack 打包之后文件会很大,减缓首屏加载速度,降低用户体验。因此,我们要使用路由懒加载,将不同路由对应的组件分割成不同的代码块,当路由被访问的时候才加载对应的组件。

2、v-if和v-show的应用

  • v-if 是真正的条件渲染,需要操作dom元素,有更高的切换消耗;
  • v-show控制的元素总是会被渲染,简单地基于 CSS 的 display 属性进行切换。因此,如果需要非常频繁的切换,建议使用v-show,如果在运行时条件很少改变,则使用v-if。

3、长列表/无限列表渲染

在我们的数据平台或者沉淀多年的业务数据可能会有几十万,上百万条数据,这时我们除了要应用分页,无限滚动的思路,最好做窗口化渲染来优化性能,只渲染可视区域内的内容,减少重新渲染组件和创建 dom 节点的时间。具体可以参考使用vue-virtual-scroll-listvue-virtual-scroller插件来实现。

4、条件语句优化

在我们的项目中经常会遇到有四五个判断条件甚至更多的情况,这时如果嵌套过多过深,就会导致代码难以理解,维护困难,也会降低运行时性能。 我们可以使用return优先返回错误语句而不使用 if else模块 也可以利用Map数据结构来判断,减少循环和更多的判断

5、代码模块化,咱们可以把很多常用的地方封装成单独的组件,在需要用到的地方引用,而不是写过多重复的代码,每一个组件都要明确含义,复用性越高越好,可配置型越强越好,包括咱们的css也可以通过less和sass的自定义css变量来减少重复代码。

6、for循环设置key值,在用v-for进行数据遍历渲染的时候,为每一项都设置唯一的key值,为了让Vue内部核心代码能更快地找到该条数据,当旧值和新值去对比的时候,可以更快的定位到diff

7、更加理解Vue的生命周期,不要造成内部泄漏,使用过后的全局变量在组件销毁后重新置为null。

8、可以使用keep-alive,keep-alive是Vue提供的一个比较抽象的组件,用来对组件进行缓存,从而节省性能。

9、按需引入,咱们使用的一些第三方库可以通过按需引入的方式加载。避免引入不需要使用的部分,无端增加项目体积。比如在使用element-ui库的时候,可以只引入需要用到的组件。

六、其他

  1. 减少 HTTP 请求

一个完整的 HTTP 请求需要经历 DNS 查找,TCP 握手,浏览器发出 HTTP 请求,服务器接收请求,服务器处理请求并发回响应,浏览器接收响应等过程。建议将多个小文件合并为一个大文件,从而减少 HTTP 请求次数的原因。

  1. 使用 HTTP2

解析速度快 多路复用 首部压缩 服务器推送

  1. 使用服务端渲染

客户端渲染: 获取 HTML 文件,根据需要下载 JavaScript 文件,运行文件,生成 DOM,再渲染。

服务端渲染:服务端返回 HTML 文件,客户端只需解析 HTML。

优点:首屏渲染快,SEO 好。

缺点:配置麻烦,增加了服务器的计算压力。

  1. 静态资源使用 CDN

内容分发网络(CDN)是一组分布在多个不同地理位置的 Web 服务器。我们都知道,当服务器离用户越远时,延迟越高。CDN 就是为了解决这一问题,在多个位置部署服务器,让用户离服务器更近,从而缩短请求时间。

  1. 将 CSS 放在文件头部,JavaScript 文件放在底部

CSS 执行会阻塞渲染,阻止 JS 执行

JS 加载和执行会阻塞 HTML 解析,阻止 CSSOM 构建

  1. 使用字体图标 iconfont 代替图片图标

字体图标就是将图标制作成一个字体,使用时就跟字体一样,可以设置属性,例如 font-size、color 等等,非常方便。并且字体图标是矢量图,不会失真。还有一个优点是生成的文件特别小。

  1. 善用缓存,不重复加载相同的资源

为了避免用户每次访问网站都得请求文件,我们可以通过添加 Expires 或 max-age 来控制这一行为。Expires 设置了一个时间,只要在这个时间之前,浏览器都不会请求文件,而是直接使用缓存。而 max-age 是一个相对时间,建议使用 max-age 代替 Expires 。

  1. 压缩文件

压缩文件可以减少文件下载时间,让用户体验性更好。得益于 webpack 和 node 的发展,现在压缩文件已经非常方便了。

  1. 图片优化

(1). 图片延迟加载

在页面中,先不给图片设置路径,只有当图片出现在浏览器的可视区域时,才去加载真正的图片,这就是延迟加载。对于图片很多的网站来说,一次性加载全部图片,会对用户体验造成很大的影响,所以需要使用图片延迟加载。

(2). 响应式图片 响应式图片的优点是浏览器能够根据屏幕大小自动加载合适的图片。

(3). 调整图片大小

例如,你有一个 1920 * 1080 大小的图片,用缩略图的方式展示给用户,并且当用户鼠标悬停在上面时才展示全图。如果用户从未真正将鼠标悬停在缩略图上,则浪费了下载图片的时间。

所以,我们可以用两张图片来实行优化。一开始,只加载缩略图,当用户悬停在图片上时,才加载大图。还有一种办法,即对大图进行延迟加载,在所有元素都加载完成后手动更改大图的 src 进行下载。

(4). 降低图片质量 压缩方法有两种,一是通过 webpack 插件 image-webpack-loader,二是通过在线网站进行压缩。

(5). 尽可能利用 CSS3 效果代替图片

(6). 使用 webp 格式的图片

  1. 通过 webpack 按需加载代码,提取第三库代码,减少 ES6 转为 ES5 的冗余代码

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。

  1. 减少重绘重排

用 JavaScript 修改样式时,最好不要直接写样式,而是替换 class 来改变样式。

如果要对 DOM 元素执行一系列操作,可以将 DOM 元素脱离文档流,修改完成后,再将它带回文档。推荐使用隐藏元素(display:none)或文档碎片(DocumentFragement),都能很好的实现这个方案。

  1. 使用事件委托

事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术, 使用事件委托可以节省内存。