前端性能优化合集

305 阅读12分钟

HTML层面:

SEO层面:

title 标题:

(1)title具有不可替代性,是我们的内页第一个重要标签,是搜索引擎了解网页的入口,和对网页主题归属的最佳判断点。

首页标题:网站名(产品名)- 网站的介绍
<title> 小米商城 - 小米11、Redmi Note 9、小米MIX Alpha,小米电视官方网站 </title>

Keywords 关键字:

Keywords是页面关键词,是搜索引擎关注点之一。Keywords应该限制在6~8个关键词左右,电商类网站可以稍多一点

<meta name="keywords" content="小米,redmi,小米11,Redmi Note 9,小米MIX Alpha,小米商城">

description 网站说明:

<meta name="description" content="小米官网直营小米公司旗下所有产品,包括小米手机系列小米11 、小米10 Pro、小米MIX Alpha,Redmi 红米系列Redmi Note 9、Redmi K30,小米电视、笔记本、米家智能家居等,同时提供小米客户服务及售后支持.">

对于关键词的作用明显降低,但由于很多搜索引擎,仍然大量采用网页的MATA标签中描述部分作为搜索结果的“内容摘要”。 就是简要说明我们网站的主要做什么的。 注意点:

  1. 描述中出现关键词,与正文内容相关,这部分内容是给人看的,所以要写的很详细,让人感兴趣, 吸引用户点击。
  2. 同样遵循简短原则,字符数含空格在内不要超过 120 个汉字。
  3. 补充在 title 和 keywords 中未能充分表述的说明.

性能优化方面:

1. script 标签:调整加载顺序提升渲染速度

由于浏览器的底层运行机制,渲染引擎在解析 HTML 时,若遇到 script 标签引用文件,则会暂停解析过程,同时通知网络线程加载文件,文件加载后会切换至 JavaScript 引擎来执行对应代码,代码执行完成之后切换至渲染引擎继续渲染页面。

建议 script 标签写在 body 标签的底部, 当渲染引擎解析 HTML 遇到 script 标签引入文件时,会立即进行一次渲染,因为当渲染引擎执行到 body 底部时会先将已解析的内容渲染出来,然后再去请求相应的 JavaScript 文件。如果是内联脚本(即不通过 src 属性引用外部脚本文件直接在 HTML 编写 JavaScript 代码的形式),渲染引擎则不会渲染已解析的内容。

如果没有 defer 或 async 属性,浏览器会立即加载并执行相应的脚本。它不会等待后续加载的文档元素,读取到就会开始加载和执行,这样就阻塞了后续文档的加载。 下图可以直观的看出三者之间的区别:

image.png

其中蓝色代表 js 脚本网络加载时间,红色代表 js 脚本执行时间,绿色代表 html 解析。

defer 和 async 属性都是去异步加载外部的 JS 脚本文件,它们都不会阻塞页面的解析,其区别如下:

执行顺序多个带 async 属性的标签,不能保证加载的顺序; 多个带 defer 属性的标签,按照加载顺序执行; 脚本是否并行执行:

  • async 属性,表示后续文档的加载和执行与 js 脚本的加载和执行是并行进行的,即异步执行;
  • defer 属性,加载后续文档的过程和 js 脚本的加载(此时仅加载不执行)是并行进行的(异步),js 脚本需要等到文档所有元素解析完成之后才执行,DOMContentLoaded 事件触发执行之前。

2. link 标签:通过预处理提升渲染速度

在我们对大型单页应用进行性能优化时,也许会用到按需懒加载的方式,来加载对应的模块,但如果能合理利用 link 标签的 rel 属性值来进行预加载,就能进一步提升渲染速度。 dns-prefetch:当 link 标签的 rel 属性值为“dns-prefetch”时,浏览器会对某个域名预先进行 DNS 解析并缓存。这样,当浏览器在请求同域名资源的时候,能省去从域名查询 IP 的过程,从而减少时间损耗。

a 标签的 dns 预解析是默认打开的, https 会关闭掉 a 标签的 dns 预解析
<!-- 强制打开 a 标签的 dns 预解析 -->
<meta http-equiv="x-dns-prefetch-control" content="on" />
<link rel="dns-prefetch" href="http://g.alicdn.com" />
<!--如果不确定是http还是https连接的话建议如下写法 -->
<link rel="dns-prefetch" href="//g.alicdn.com">
  • dns-prefetch 应该尽量的放在网页的前面,推荐放在 后面

  • dns-prefetch需慎用,多页面重复DNS预解析会增加重复DNS查询次数

  • preconnect。让浏览器在一个 HTTP 请求正式发给服务器前预先执行一些操作,这包括 DNS 解析、TLS 协商、TCP 握手,通过消除往返延迟来为用户节省时间。

  • prefetch/preload。两个值都是让浏览器预先下载并缓存某个资源,但不同的是,prefetch 可能会在浏览器忙时被忽略,而 preload 则是一定会被预先下载。

<link rel="preload" href="路径" as="资源类型" />
  • prerender。浏览器不仅会加载资源,还会解析执行页面,进行预渲染。

image.png


css层面:

加载性能:

(1)css 压缩:将写好的 css 进行打包压缩,可以减小文件体积。

(2)css 单一样式:当需要下边距和左边距的时候,很多时候会选择使用 margin:top 0 bottom 0;但 margin-bottom:bottom;margin-left:left;执行效率会更高。

(3)减少使用@import,建议使用 link,因为后者在页面加载时一起加载,前者是等待页面加载完成之后再进行加载。

选择器性能:

(1)关键选择器(key selector)。选择器的最后面的部分为关键选择器(即用来匹配目标元素的部分)。CSS 选择符是从右到左进行匹配的。当使用后代选择器的时候,浏览器会遍历所有子元素来确定是否是指定的元素等等;

(2)如果规则拥有 ID 选择器作为其关键选择器,则不要为规则增加标签。过滤掉无关的规则(这样样式系统就不会浪费时间去匹配它们了)。

(3)避免使用通配规则,如*{}计算次数惊人,只对需要用到的元素进行选择。

(4)尽量少的去对标签进行选择,而是用 class。

(5)尽量少的去使用后代选择器,降低选择器的权重值。后代选择器的开销是最高的,尽量将选择器的深度降到最低,最高不要超过三层,更多的使用类来关联每一个标签元素。

(6)了解哪些属性是可以通过继承而来的,然后避免对这些属性重复指定规则。

渲染性能:

(1)慎重使用高性能属性:浮动、定位。

(2)尽量减少页面重排、重绘。

(3)去除空规则:{}。空规则的产生原因一般来说是为了预留样式。去除这些空规则无疑能减少 css 文档体积。

(4)属性值为 0 时,不加单位。

(5)属性值为浮动小数 0.**,可以省略小数点之前的 0。

(6)标准化各种浏览器前缀:带浏览器前缀的在前。标准属性在后。

(7)不使用@import 前缀,它会影响 css 的加载速度。

(8)选择器优化嵌套,尽量避免层级过深。

(9)css 雪碧图,同一页面相近部分的小图标,方便使用,减少页面的请求次数,但是同时图片本身会变大,使用时,优劣考虑清楚,再使用。

(10)正确使用 display 的属性,由于 display 的作用,某些样式组合会无效,徒增样式体积的同时也影响解析性能。

(11)不滥用 web 字体。对于中文网站来说 WebFonts 可能很陌生,国外却很流行。web fonts 通常体积庞大,而且一些浏览器在下载 web fonts 时会阻塞页面渲染损伤性能。

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

压缩字体文件

使用 fontmin-webpack 插件对字体文件进行压缩(感谢前端小伟提供)。

参考资料:

  • fontmin-webpack
  • Iconfont-阿里巴巴矢量图标库 (13)使用 transform 和 opacity 属性更改来实现动画。在 CSS 中,transforms 和 opacity 这两个属性更改不会触发重排与重绘,它们是可以由合成器(composite)单独处理的属性。

image.png

参考资料:

可维护性、健壮性:

(1)将具有相同属性的样式抽离出来,整合并通过 class 在页面中进行使用,提高 css 的可维护性。

(2)样式与内容分离:将 css 代码定义到外部 css 中。


javascript层面:

script标签的位置

  1. script标签可以阻塞渲染,从而延迟页面在浏览器中的显示。
  2. 将script标签放在文档底部可以加快页面渲染速度。

如果可以管理脚本的执行,则使用async属性(异步加载)可以进一步提供性能。

  1. 引申出模块化(比如 jq.js 和a.js 同时async,a.js依赖jq,a.js因为小,所以必然先加载,此时jq还没初始化,就会报错)
  2. 管理使用async的相互依赖的脚本的执行可能是一项挑战。第三方脚本加载库(如Alameda或RequireJS)可以为管理脚本依赖项提供一个方便的接口,同时还能提供异步加载和执行脚本的好处。
  • webpack工程化,可以使用 import('moment').then(moment => { xxx })

js动画

简单的可以直接用css动画,要写js动画的话: 用requestAnimationFrame 替代定时器(能提高 渲染和绘制 速度), 还有velocity动画库(可以脱离jq动画)

大多数设备屏幕刷新率为 60 次/秒,也就是说每一帧的平均时间为 16.66 毫秒。在使用 JavaScript 实现动画效果的时候,最好的情况就是每次代码都是在帧的开头开始执行。而保证 JavaScript 在帧开始时运行的唯一方式是使用 requestAnimationFrame

/**
 * If run as a requestAnimationFrame callback, this
 * will be run at the start of the frame.
 */
function updateScreen(time) {
  // Make visual updates here.
}

requestAnimationFrame(updateScreen);
复制代码

如果采取 setTimeoutsetInterval 来实现动画的话,回调函数将在帧中的某个时点运行,可能刚好在末尾,而这可能经常会使我们丢失帧,导致卡顿。

image.png

高效的dom操作

浏览器渲染过程

  1. 解析HTML生成DOM树。
  2. 解析CSS生成CSSOM规则树。
  3. 解析JS,操作 DOM 树和 CSSOM 规则树。
  4. 将DOM树与CSSOM规则树合并在一起生成渲染树。
  5. 遍历渲染树开始布局,计算每个节点的位置大小信息。
  6. 浏览器将所有图层的数据发送给GPU,GPU将图层合成并显示在屏幕上。

image.png

重排

当改变 DOM 元素位置或大小时,会导致浏览器重新生成渲染树,这个过程叫重排。

重绘

当重新生成渲染树后,就要将渲染树每个节点绘制到屏幕,这个过程叫重绘。不是所有的动作都会导致重排,例如改变字体颜色,只会导致重绘。记住,重排会导致重绘,重绘不会导致重排 。

重排和重绘这两个操作都是非常昂贵的,因为 JavaScript 引擎线程与 GUI 渲染线程是互斥,它们同时只能一个在工作。

什么操作会导致重排?

  • 添加或删除可见的 DOM 元素
  • 元素位置改变
  • 元素尺寸改变
  • 内容改变
  • 浏览器窗口尺寸改变

如何减少重排重绘?

  • 用 JavaScript 修改样式时,最好不要直接写样式,而是替换 class 来改变样式。
  • 如果要对 DOM 元素执行一系列操作,可以将 DOM 元素脱离文档流,修改完成后,再将它带回文档。 推荐使用隐藏元素(display:none)或文档碎片(DocumentFragement),都能很好的实现这个方案。

js操作优化

JS 各种循环性能对比

  • 结论:性能:普通 for 循环 > forEach > for of > map > for in

    • 其中forEach和map不能在遍历中停止

计算密集型或高延迟的任务 用 web worker

核心:一些计算密集型或高延迟的任务,可以用web worker处理

JavaScript 语言采用的是单线程模型,所有任务只能在一个线程上完成,一次只能做一件事。

  • 前面的任务没做完,后面的任务只能等着。随着电脑计算能力的增强,尤其是多核 CPU 的出现,单线程带来很大的不便,无法充分发挥计算机的计算能力。

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。

  • 在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。
  • 等到 Worker 线程完成计算任务,再把结果返回给主线程。

好处是:

  • 一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。

  • 这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭

使用:

var worker = new Worker('oneWork.js'); // Worker()构造函数的参数是一个脚本文件,该文件就是 Worker 线程所要执行的任务。由于 Worker 不能读取本地文件,所以这个脚本必须来自网络。如果下载没有成功(比如404错误),Worker 就会默默地失败。

worker.postMessage('Hello World'); // 可以发消息给 oneWork.js
worker.postMessage({method: 'echo', args: ['Work']}); // 可以发消息给 oneWork.js

worker.onmessage = function (event) { // 监听oneWork.js
  console.log('Received message ' + event.data); // 得到oneWork.js的返回值
  doSomething(event.data);
}

function doSomething(data) {
  console.log('Received message ' + data);
  // 执行任务 
  ...
  // 执行完了
  worker.postMessage('Work done!');
}

// Worker完成任务后,需关闭worker (比如放在vue的 beforeDestory(){} 里面)
worker.terminate();

离线缓存 用 service worker

ServiceWorker 是运行在浏览器后台进程里的一段 JS,它可以做许多事情,比如拦截客户端的请求、向客户端发送消息、向服务器发起请求等等,其中最重要的作用之一就是离线资源缓存。

ServiceWorker 拥有对缓存流程丰富灵活的控制能力,当页面请求到 ServiceWorker 时,ServiceWorker 同时请求缓存和网络,把缓存的内容直接给用户,而后覆盖缓存,我司已经使用了 ServiceWorker 替换 HTTP缓存策略

注意:需要HTTPS才可以使用 ServiceWorker

可参考:www.zhangxinxu.com/wordpress/2…