前端性能优化面面谈[1]-概述

作为一名前端工程师,使用HTML、CSS开发出页面UI,使用 JavaScript 实现业务功能逻辑是我们的必备素质,而除此之外,是否能够对一个网站、WebApp、HibirdApp进行性能优化,从而使得他们在前端加载、显示的速度提升到一个技术上认可的程度,则是评判一名前端工程师是否优秀的标准。

在前后端分离的大背景下,我们从后端工程师的宏观职责上来看,也能得出如上的结论。正如后端工程师除了完成业务逻辑之外,要通过负载均衡、数据库调优、缓存、系统架构优化等手段,来实现更快速的处理和响应。前端的职责理应也应该不止于完成业务功能,毕竟,一个系统起码在技术上能够认为是成功的话要看两个指标: [能用][好用]

为什么要在前端进行性能优化

上面说了 从工程师能力方面,前端性能优化是必要的。

而从产品角度,技术上的页面访问 成功率 直接决定了产品的 蹦失率,也就是我们页面加载很慢(超过3s甚至到4-5s的话), 用户肯定有很多不愿意等待,如果是注册页面,这个新用户就流失了;如果是购买页面,这比交易就丢掉了。(哪怕这个用户将来会再回来重试下,那么这个50%的丢失概率也应该是我们程序员的锅…). 所以我们应该努力通过技术手段保证留存用户,而不能让产品有任何技术方面的吐槽和反馈

那么,为什么性能优化要在前端做呢? 据说,Facebook 曾做过统计后端处理数据的耗时在整个网页性能耗时中只占了 10%-20% 左右,大量的耗时是发生在网络传输和页面渲染期间。因此前端性能优化更具有性价比。

优化思路

优化方法有很多,为了更方便我们大脑记忆。我认为可以从页面渲染的这个流程来分类记忆。

本文也主要讲解 移动app 内的 hybird 类型的页面。这种页面加载过程大概有以下几个阶段:

  1. webview 响应用户的 click 点击按钮的行为,立刻进行webview的实例化和初始化
  2. webview 去加载要显示的URL(其中内置包含了 DNS 解析、TCP 握手等过程)
  3. webview 加载页面的 html 文档并解析 html 和加载其中的 css、js 等资源。解析完成 css 会构建样式树,解析完成 html 会构建 dom 树。此步骤执行后,用户可以看到基本的页面。
  4. JavaScript 代码执行时可能存在 ajax 异步数据的拉取和渲染。例如动态拉取远程数据并填充到页面某个区域(如评论区)
  5. 上述步骤都执行完毕,用户才能看到完整的一个页面

基于浏览器或webview的以上行为,进而推断出我们性能优化的方法也应该从如下几个方面入手:

  1. webview初始化的效率提升
  2. 网络传输资源的速度的提升
  3. 渲染动态数据的性能提升
  4. 服务端响应请求的处理效率提升

测速深入优化
1、spdy、http2.0、qick预研和尝试
2、httpdns
3、直出缓存架构
4、图片自适应方案向BG推广

常用方法

网络传输

这里主要有 2 个思路,一个是减少请求数量,一个是减少传输体积。

  • 渲染速率

  • 直出, 这主要得益于 直接的 html dom 构建和渲染相对于 ajax 拉取数据再渲染 还是要快一些的

最近在重构一个简单的页面,有幸可以对其性能的各个影响点进行了一些处理。我参考了km里面的一些通用做法,尽量把能做的都用在我们的页面中。
优化点介绍:

  1. 用上304,除掉200。未变化的资源避免重复下载。 ajax也可以支持304,发现后端express框架里面默认对Get的动态请求已经用etag头判断304了,但考察了下百度ajax的get并没有使用304(这个影响也不大,用不用没什么关系)。
    技术点:lastModify和etag头
  2. 消灭304,用上本地cache。该cache的资源全本地cache,避免走一趟服务端,除了入口页index.html之外,全部缓存30天或一年,甚至n年(反正一旦网站更新版本,所有资源的hash都变了)。 备注:ajax动态请求不可使用本地缓存。
    技术点:cache-control或expire头
    http://km.oa.com/group/11800/articles/show/165331
    补充:该方式比通过localStorage靠谱的地方是 —— 手动刷新页面可以重新加载资源

  3. 启用gzip。所有资源在服务器端都应该开启gzip压缩。

  4. 消灭前端jsonrpc影子。从符合现代http请求规范的角度,稍微优化了下前端请求格式,尽管后端微服务依旧是jsonrpc格式,但有中间层的存在之后,前端可以消除这种post模式(因为post占用资源相对多一点,可参看第5条),从而减少请求时全用post带来的性能问题。
  5. 启用轻量级的get请求。参考km文章:“首先性能因素不应该是考虑使用get还是post的主要原因,首先关注的应该是否符合HTTP中标准中的约定,get应该用做数据的获取而不是提交。之所以用get性能更好的原因是有测试表明,即使数据很小,大部分浏览器(除了Firefox)在使用post时也会发送两个TCP的packet,所以性能上会有损失” 。我们Obs系统中有大量的ajax接口是数据获取,理应使用get请求替代post。
  6. keep-alive连接复用。改善http头中的connection字段,设置为keep-alive(这在http1.1之后都是默认的了),可充分减少TCP连接建立的开销。而目前我们系统中haproxy可能需复用之前的策略,设置了connection: close,这里怕影响之前的系统,故这一条优化留在以后再考虑。
    之所以以前HAProxy没有开启keepAlive,可能是如下原因:

  7. cookie优化。访问obs.oa.com下的所有内容,都会带上oa域下所有cookie,这就增加了大量额外的请求体积,(而node中间层无论进行tof验证还是自己业务需求,都仅需要obs这个子域下的cookie就够了)。在这一点上,除非公司可以提供非oa域的cdn来放置静态资源,否则没有办法清除掉请求上所携带的主域cookie。目前只能尽量将公共css,js等资源放到res.oa.com上,这样请求这些静态资源的时候,就不会携带不必要的obs子域下的cookie了(但主域的cookie还是会带上)。

  8. don’t use @import。 一般css我们讲究早加载,与dom一起渲染,避免dom重绘。而@import的引入方法会导致css迟加载,导致页面先渲染了没有css的dom,然后重绘更新,用户体验比较差。
  9. 首屏服务端直出。SPA应用的痛点就是首屏渲染问题。幸运的是vue2.0将直接支持node层SSR(server side render)服务端渲染。待我们以后研究一下vue2.0,再来进行相关的优化实践。

##
合并连接数,SPDY多路复用,离线方案,直出方案,流量控制

;减少http请求
使用cdn
添加缓存头
启用gzip
css放在页面上面,帮助浏览器尽快形成样式树和渲染树
script放在页面尾部,减少js逻辑对首屏渲染的阻塞
避免css中使用expression,一般不会有的
js和css放到外部文件,减少页面体积
减少dns查询,DNS提前解析 link rel=”dns-prefetch” href=”http://vip.qq.com"
压缩js和css
避免重定向
移除重复脚本
配置实体标签etag
使ajax缓存
小文件尽量合并,单个域名并发链接数不要超过4个;
手Q支持离线包的能力,可以存储在sd卡里。可以把类库放到离线包里,即使缓存过期了也可以快速从离线包加载。
借用localStorage存储一些ajax请求,可以实现优先使用localStorage的展示。
多页应用拆解公共部分长久缓存,多个页面可以只加载一次公共部分,或者业务变化时保持了公共部分不加载