# 性能优化

# 重构的思考

网站重构是指在不改变外部行为的前提下,简化结构、添加可读性,且在网站前端保持一致的行为。也就是说,在不改变 UI 的情况下,对网站进行优化,在扩展的同时保持一致的 UI。 对于传统的网站来说,重构通常包括以下方面。

  • 把表格( table)布局改为 DV+CSS。
  • 使网站前端兼容现代浏览器。
  • 对移动平台进行优化。
  • 针对搜索引擎进行优化。
  • 深层次的网站重构应该考虑以下方面。
    • 减少代码间的耦合
    • 让代码保持弹性。
    • 严格按规范编写代码。
    • 设计可扩展的 API。
    • 代替旧的框架、语言
    • 增强用户体验。
    • 对加载速度进行优化。
    • 压缩 JavaScript、CSS、 image 等前端资源。
    • 优化程序的性能(如数据读写)。
    • 采用 CDN 来加速资源加载。
    • 优化 JavaScript DOM。
    • 缓存 HTTP 服务器的文件。

# 图片优化

对于图片懒加载,可以为页面添加一个滚动条事件,判断图片是否在可视区域内或者即将进入可视区域,优先加载。

  • 如果为幻灯片、相册文件等,可以使用图片预加载技术,对于当前展示图片的前一张图片和后一张图片优先下载。
  • 如果图片为 CSS 图片,可以使用 CSS Sprite、SVG sprite、 Icon font、Base64 等技术。
  • 如果图片过大,可以使用特殊编码的图片,加载时会先加载一张压缩得特别小的缩略图,以提高用户体验。
  • 如果图片展示区域小于图片的真实大小,则应在服务器端根据业务需要先行进行图片压缩,图片压缩后,图片大小与展示的就一致了。
  • 选择合适的图片格式(颜色数多用 JPG 格式,而很少使用 PNG 格式,如果能通过服务器端判断浏览器支持 WebP 就用 WebP 或 SVG 格式)
  • 使用tinypng (opens new window)网站压缩图片大小

# 渲染优化

  • 缓存 Ajax,使用 CDN、外部 JavaScript 和 CSS 文件缓存,添加 Expires 头,在服务器端配置 Etag,减少 DNS 查找等。
  • 合并样式和脚本,使用 CSS 图片精灵,初始首屏之外的图片资源按需加载,静态资源延迟加载。
  • CSS 代码:避免使用 CSS 表达式、高级选择器、通配选择器。
  • JavaScript 代码:用散列表来优化查找,少用全局变量,用 innerHTML 代替 DOM 操作,减少 DOM 操作次数,优化 JavaScript 性能,用 setTimeout 避免页面失去响应,缓存 DOM 节点查找的结果,避免使用 with(with 会创建自己的作用域,增加作用域链的长度),多个变量声明合并。
  • HTML 代码:避免图片和 iFrame 等 src 属性为空。src 属性为空,会重新加载当前页面,影响速度和效率,尽量避免在 HTML 标签中写 Style 属性
  • 在网址后加斜杠
  • 为图片标明高度和宽度(如果浏览器没有找到这两个参数,它需要一边下载图片一边计算大小。如果图片很多,浏览器需要不断地调整页面。这不但影响速度,而且影响浏览体验。当浏览器知道高度和宽度参数后,即使图片暂时无法显示,页面上也会腾出图片的空位,然后继续加载后面的内容,从而优化加载时间,提升浏览体验)。
  • 减少 HTTP 请求次数,控制 CSS Sprite、JavaScript 与 CSS 源码、图片的大小,使用网页 Gzip、CDN 托管、data 缓存、图片服务器
  • 通过前端模板 JavaScript 和数据,减少由于 HTML 标签导致的带宽浪费,在前端用变量保存 Ajax 请求结果,每次操作本地变量时,不用请求,减少请求次数。
  • 用 innerHTML 代替 DOM 操作,减少 DOM 操作次数,优化 JavaScript 性能。
  • 当需要设置的样式很多时,设置 className 而不是直接操作 Style。
  • 少用全局变量,缓存 DOM 节点查找的结果,减少 I/O 读取操作
  • 避免使用 CSS 表达式,它又称动态属性。
  • 预加载图片,将样式表放在顶部,将脚本放在底部,加上时间戳。
  • 避免在页面的主体布局中使用 table,table 要在其中的内容完全下载之后才会显示出来,显示的速度比 DIV+CSS 布局慢。

# 移动端优化

  • 尽量使用 CSS3 动画,开启硬件加速。
  • 适当使用 touch 事件代替 click 事件。
  • 避免使用 CSS3 渐变阴影效果。
  • 可以用 transform:translateZ(0)来开启硬件加速。
  • 不滥用 Float, Float 在渲染时计算量比较大,尽量少使用。
  • 不滥用 Web 字体,Web 字体需要下载、解析、重绘当前页面,尽量少使用。
  • 合理使用 requestAnimation Frame 动画代替 setTimeout。
  • 合理使用 CSS 中的属性(CSS3 transitions、CSS3 3D transforms、 - Opacity、 Canvas、 WebGL、Video)触发 GPU 渲染。过度使用会使手机耗电量増加。

# 文件优化

  • 可以进行文件合并、文件压缩使文件最小化;
  • 可以使用 CDN 托管文件,让用户更快速地访问;
  • 可以使用多个域名来缓存静态文件。

# 针对代码优化性能

利用性能分析工具监测性能,包括静态 Analyze 工具和运行时的 Profile 工具(在 Xcode 工具栏中依次单击 Product→ Profile 项可以启动)。

比如测试程序的运行时间,当单击 Time Profiler 项时,应用程序开始运行,这就能获取到运行整个应用程序所消耗时间的分布和百分比。为了保证数据分析在同一使用场景下的真实性,一定要使用真机,因为此时模拟器在 Mac 上运行,而 Mac 上的 CPU 往往比 iOS 设备要快。

# 针对 CSS 优化性能

具体优化方法如下。

(1)正确使用 display 属性, display 属性会影响页面的渲染,因此要注意以下几方面。

display:inline 后不应该再使用 width、 height、 margin、 padding 和 float 。

display:inline- block 后不应该再使用 float。

display:block 后不应该再使用 vertical-align。

display:table-*后不应该再使用 margin 或者 float。

(2)不滥用 float。

(3)不声明过多的 font-size。

(4)当值为 0 时不需要单位。

(5)标准化各种浏览器前缀,并注意以下几方面。

浏览器无前缀应放在最后。

CSS 动画只用( -webkit-无前缀)两种即可。

其他前缀包括 -webkit-、-moz-、-ms-、无前缀( Opera 浏览器改用 blink 内核,所以-0-被淘汰)

(6)避免让选择符看起来像是正则表达式。高级选择器不容易读懂,执行时间也长。

(7)尽量使用 id、 class 选择器设置样式(避免使用 style 属性设置行内样式)

(8)尽量使用 CSS3 动画。

(9)减少重绘和回流。

# 针对 HTML 优化性能

(1)对于资源加载,按需加载和异步加载

(2)首次加载的资源不超过 1024KB,即越小越好。

(3)压缩 HTML、CSS、 JavaScript 文件。

(4)减少 DOM 节点。

(5)避免空 src(空 src 在部分浏览器中会导致无效请求)。

(6)避免 30*、40*、50*请求错误

(7)添加 Favicon.ico,如果没有设置图标 ico,则默认的图标会导致发送一个 404 或者 500 请求。

# 针对 JavaScript 优化性能

(1)缓存 DOM 的选择和计算。

(2)尽量使用事件委托模式,避免批量绑定事件。

(3)使用 touchstart、 touchend 代替 click。

(4)合理使用 requestAnimationFrame 动画代替 setTimeout。

(5)适当使用 canvas 动画。

(6)尽量避免在高频事件(如 TouchMove、 Scroll 事件)中修改视图,这会导致多次渲染。

# 如何优化服务器端

(1)启用 Gzip 压缩。

(2)延长资源缓存时间,合理设置资源的过期时间,对于一些长期不更新的静态资源过期时间设置得长一些。

(3)减少 cookie 头信息的大小,头信息越大,资源传输速度越慢。

(4)图片或者 CSS、 JavaScript 文件均可使用 CDN 来加速。

# 如何优化服务器端的接口

(1)接口合并:如果一个页面需要请求两部分以上的数据接口,则建议合并成一个以减少 HTTP 请求数。

(2)减少数据量:去掉接口返回的数据中不需要的数据。

(3)缓存数据:首次加载请求后,缓存数据;对于非首次请求,优先使用上次请求的数据,这样可以提升非首次请求的响应速度。

# 如何优化脚本的执行

脚本处理不当会阻塞页面加载、渲染,因此在使用时需注意。

(1)把 CSS 写在页面头部,把 JavaScript 程序写在页面尾部或异步操作中。

(2)避免图片和 iFrame 等的空 src,空 src 会重新加载当前页面,影响速度和效率。

(3)尽量避免重设图片大小。重设图片大小是指在页面、CSS、 JavaScript 文件等中多次重置图片大小,多次重设图片大小会引发图片的多次重绘,影响性能

(4)图片尽量避免使用 DataURL。DataURL 图片没有使用图片的压缩算法,文件会变大,并且要在解码后再渲染,加载慢,耗时长。

# 如何优化渲染

(1)通过 HTML 设置 Viewport 元标签, Viewport 可以加速页面的渲染,如以下代码所示。

<meta name="viewport" content="width=device=width,initial-scale=1" />

(2)减少 DOM 节点数量,DOM 节点太多会影响页面的渲染,应尽量减少 DOM 节点数量。

(3)尽量使用 CSS3 动画,合理使用 requestAnimationFrame 动画代替 setTimeout,适当使用 canvas 动画(5 个元素以内使用 CSS 动画,5 个元素以上使用 canvas 动画(iOS 8 中可使用 webGL))。

(4)对于高频事件优化 Touchmove, Scroll 事件可导致多次渲染。

使用 requestAnimationFrame 监听帧变化,以便在正确的时间进行渲染,增加响应变化的时间间隔,减少重绘次数。

使用节流模式(基于操作节流,或者基于时间节流),减少触发次数。

(5)提升 GPU 的速度,用 CSS 中的属性(CSS3 transitions、CSS3 3D transforms、 Opacity、 Canvas、 WebGL、Video)来触发 GPU 渲染。

# 如何设置 DNS 缓存

在浏览器地址栏中输入 URL 以后,浏览器首先要查询域名( hostname)对应服务器的 IP 地址,一般需要耗费 20~120ms 的时间。 DNS 查询完成之前,浏览器无法识别服务器 IP,因此不下载任何数据。基于性能考虑,ISP 运营商、局域网路由、操作系统、客户端(浏览器)均会有相应的 DNS 缓存机制。

(1)正 IE 缓存 30min,可以通过注册表中 DnsCacheTimeout 项设置。

(2) Firefox 混存 1 min,通过 network.dnsCacheExpiration 配置。

(3)在 Chrome 中通过依次单击“设置”→“选项”→“高级选项”,并勾选“用预提取 DNS 提高网页载入速度”选项来配置缓存时间。

# 什么时候会出现资源访问失败

开发过程中,发现很多开发者没有设置图标,而服务器端根目录也没有存放默认的 Favicon.ico,从而导致请求 404 出现。通常在 App 的 webview 里打开 Favicon.ico,不会加载这个 Favicon.ico,但是很多页面能够分享。

如果用户在浏览器中打开 Favicon. ico,就会调取失败,一般尽量保证该图标默认存在,文件尽可能小,并设置一个较长的缓存过期时间。另外,应及时清理缓存过期导致岀现请求失败的资源。

# jQuery 性能优化

(1)使用最新版本的 jQuery 类库。

JQuery 类库每一个新的版本都会对上一个版本进行 Bug 修复和一些优化,同时也会包含一些创新,所以建议使用最新版本的 jQuery 类库提高性能。不过需要注意的是,在更换版本之后,不要忘记测试代码,毕竟有时候不是完全向后兼容的。

(2)使用合适的选择器。 jQuery 提供非常丰富的选择器,选择器是开发人员最常使用的功能,但是使用不同选择器也会带来性能问题。建议使用简凖选择器,如 i 选择器、类选择器,不要将 i 选择器嵌套等。

(3)以数组方式使用 jQuery 对象。

使用 jQuery 选择器获取的结果是一个 jQuery 对象。然而, jQuery 类库会让你感觉正在使用一个定义了索引和长度的数组。在性能方面,建议使用简单的 for 或者 while 循环来处理,而不是$. each(),这样能使代码更快。

(4)每一个 JavaScript 事件(例如 click、 mouseover 等)都会冒泡到父级节点。当需要给多个元素绑定相同的回调函数时,建议使用事件委托模式。

(5)使用 join( )来拼接字符串。

使用 join( )拼接长字符串,而不要使用“+”拼接字符串,这有助于性能优化,特别是处理长字符串的时候。

(6)合理利用 HTML5 中的 data 属性。

HTML5 中的 data 属性有助于插入数据,特别是前、后端的数据交换;jQuery 的 data( )方法能够有效地利用 HTML5 的属性来自动获取数据。

# 提升移动端 CSS3 动画体验方法

(1)尽可能多地利用硬件能力,如使用 3D 变形来开启 GPU 加速,例如以下代码。

-webkit-transform: translate 3d (0, 0, 0);
-moz-transform: translate3d(0, 0, 0);
-ms-transform: translate 3d (0, 0, 0);
transform: translate3d(0, 0, 0);

一个元素通过 translate3d 右移 500X 的动画流畅度会明显优于使用 left 属性实现的动画移动,原因是 CSS 动画属性会触发整个页面重排、重绘、重组。paint 通常是最耗性能的,尽可能避免使用触发 paint 的 CSS 动画属性。

如果动画执行过程中有闪烁(通常发生在动画开始的时候),可以通过如下方式处理。

-webkit-backface-visibility:hidden;
-moz-backface-visibility:hidden;
-ms-backface-visibility:hidden ;
backface-visibility:hidden;
-webkit-perspective:1000;
-moz-perspective:1000;
-ms-perspective:1000;
perspective:1000;

(2)尽可能少使用 box- shadows 和 gradients,它们往往严重影响页面的性能,尤其是在一个元素中同时都使用时。

(3)尽可能让动画元素脱离文档流,以减少重排,如以下代码所示。

position:fixed;
position:absolute;

# 获取 Performance 指标

/**
 * @desc 获取Performance指标
 * @return {object}
 */
export const getPerformance = () => {
  const t = window.performance.timing
  // http://javascript.ruanyifeng.com/bom/performance.html
  const performances = {
    plt: {
      t: t.loadEventStart - t.navigationStart,
      desc: '页面加载耗时'
    },
    frt: {
      t: t.loadEventEnd - t.navigationStart,
      desc: '首次渲染时长(全部事件注册时长)'
    },
    dns: {
      t: t.domainLookupEnd - t.domainLookupStart,
      desc: '域名解析耗时'
    },
    tcp: {
      t: t.connectEnd - t.connectStart,
      desc: 'TCP耗时'
    },
    ttfb: {
      t: t.responseStart - t.navigationStart,
      desc: '白屏时间'
    },
    rqt: {
      t: t.responseEnd - t.requestStart,
      desc: '从发出请求到收到全部字节的时间'
    },
    dit: {
      t: t.domInteractive - t.domLoading,
      desc: 'dom解析时间,不包括资源,到DOMContentLoaded为止'
    },
    dlt: {
      t: t.domComplete - t.domLoading,
      desc: 'dom加载时间,包括所有资源'
    }
  }
  return performances
}
  1. script 标签调整加载顺序提升渲染速度

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

为了减少这些时间损耗,可以借助 script 标签的 3 个属性来实现。

  • async 属性。立即请求文件,但不阻塞渲染引擎,而是文件加载完毕后阻塞渲染引擎并立即执行文件内容。
  • defer 属性。立即请求文件,但不阻塞渲染引擎,等到解析完 HTML 之后再执行文件内容。
  • HTML5 标准 type 属性,对应值为“module”。让浏览器按照 ECMA Script 6 标准将文件当作模块进行解析,默认阻塞效果同 defer,也可以配合 async 在请求完成后立即执行。
  1. link 标签通过预处理提升渲染速度

dns-prefetch。当 link 标签的 rel 属性值为“dns-prefetch”时,浏览器会对某个域名预先进行 DNS 解析并缓存。这样,当浏览器在请求同域名资源的时候,能省去从域名查询 IP 的过程,从而减少时间损耗。

<link ref="dns-prefetch" href="//static.com" />