前端性能优化面面谈[1]-概述
作为一名前端工程师,使用HTML、CSS开发出页面UI,使用 JavaScript 实现业务功能逻辑是我们的必备素质,而除此之外,是否能够对一个网站、WebApp、HibirdApp进行性能优化,从而使得他们在前端加载、显示的速度提升到一个技术上认可
的程度,则是评判一名前端工程师是否优秀的标准。
在前后端分离的大背景下,我们从后端工程师的宏观职责上来看,也能得出如上的结论。正如后端工程师除了完成业务逻辑之外,要通过负载均衡、数据库调优、缓存、系统架构优化等手段,来实现更快速的处理和响应。前端的职责理应也应该不止于完成业务功能,毕竟,一个系统起码在技术上能够认为是成功的话要看两个指标: [能用]
和 [好用]
为什么要在前端进行性能优化
上面说了 从工程师能力方面,前端性能优化是必要的。
而从产品角度,技术上的页面访问 成功率
直接决定了产品的 蹦失率
,也就是我们页面加载很慢(超过3s甚至到4-5s的话), 用户肯定有很多不愿意等待,如果是注册页面,这个新用户就流失了;如果是购买页面,这比交易就丢掉了。(哪怕这个用户将来会再回来重试下,那么这个50%的丢失概率也应该是我们程序员的锅…). 所以我们应该努力通过技术手段保证留存用户,而不能让产品有任何技术方面的吐槽和反馈
那么,为什么性能优化要在前端做呢? 据说,Facebook 曾做过统计后端处理数据的耗时在整个网页性能耗时中只占了 10%-20% 左右,大量的耗时是发生在网络传输和页面渲染期间。因此前端性能优化更具有性价比。
优化思路
优化方法有很多,为了更方便我们大脑记忆。我认为可以从页面渲染的这个流程来分类记忆。
本文也主要讲解 移动app 内的 hybird 类型的页面。这种页面加载过程大概有以下几个阶段:
- webview 响应用户的 click 点击按钮的行为,立刻进行webview的实例化和初始化
- webview 去加载要显示的URL(其中内置包含了 DNS 解析、TCP 握手等过程)
- webview 加载页面的 html 文档并解析 html 和加载其中的 css、js 等资源。解析完成 css 会构建样式树,解析完成 html 会构建 dom 树。此步骤执行后,用户可以看到基本的页面。
- JavaScript 代码执行时可能存在 ajax 异步数据的拉取和渲染。例如动态拉取远程数据并填充到页面某个区域(如评论区)
- 上述步骤都执行完毕,用户才能看到完整的一个页面
基于浏览器或webview的以上行为,进而推断出我们性能优化的方法也应该从如下几个方面入手:
- webview初始化的效率提升
- 网络传输资源的速度的提升
- 渲染动态数据的性能提升
- 服务端响应请求的处理效率提升
测速深入优化
1、spdy、http2.0、qick预研和尝试
2、httpdns
3、直出缓存架构
4、图片自适应方案向BG推广
常用方法
网络传输
这里主要有 2 个思路,一个是减少请求数量,一个是减少传输体积。
最近在重构一个简单的页面,有幸可以对其性能的各个影响点进行了一些处理。我参考了km里面的一些通用做法,尽量把能做的都用在我们的页面中。
优化点介绍:
- 用上304,除掉200。未变化的资源避免重复下载。 ajax也可以支持304,发现后端express框架里面默认对Get的动态请求已经用etag头判断304了,但考察了下百度ajax的get并没有使用304(这个影响也不大,用不用没什么关系)。
技术点:lastModify和etag头 消灭304,用上本地cache。该cache的资源全本地cache,避免走一趟服务端,除了入口页index.html之外,全部缓存30天或一年,甚至n年(反正一旦网站更新版本,所有资源的hash都变了)。 备注:ajax动态请求不可使用本地缓存。
技术点:cache-control或expire头
http://km.oa.com/group/11800/articles/show/165331
补充:该方式比通过localStorage靠谱的地方是 —— 手动刷新页面可以重新加载资源启用gzip。所有资源在服务器端都应该开启gzip压缩。
- 消灭前端jsonrpc影子。从符合现代http请求规范的角度,稍微优化了下前端请求格式,尽管后端微服务依旧是jsonrpc格式,但有中间层的存在之后,前端可以消除这种post模式(因为post占用资源相对多一点,可参看第5条),从而减少请求时全用post带来的性能问题。
- 启用轻量级的get请求。参考km文章:“首先性能因素不应该是考虑使用get还是post的主要原因,首先关注的应该是否符合HTTP中标准中的约定,get应该用做数据的获取而不是提交。之所以用get性能更好的原因是有测试表明,即使数据很小,大部分浏览器(除了Firefox)在使用post时也会发送两个TCP的packet,所以性能上会有损失” 。我们Obs系统中有大量的ajax接口是数据获取,理应使用get请求替代post。
keep-alive连接复用。改善http头中的connection字段,设置为keep-alive(这在http1.1之后都是默认的了),可充分减少TCP连接建立的开销。而目前我们系统中haproxy可能需复用之前的策略,设置了connection: close,这里怕影响之前的系统,故这一条优化留在以后再考虑。
之所以以前HAProxy没有开启keepAlive,可能是如下原因:cookie优化。访问obs.oa.com下的所有内容,都会带上oa域下所有cookie,这就增加了大量额外的请求体积,(而node中间层无论进行tof验证还是自己业务需求,都仅需要obs这个子域下的cookie就够了)。在这一点上,除非公司可以提供非oa域的cdn来放置静态资源,否则没有办法清除掉请求上所携带的主域cookie。目前只能尽量将公共css,js等资源放到res.oa.com上,这样请求这些静态资源的时候,就不会携带不必要的obs子域下的cookie了(但主域的cookie还是会带上)。
- don’t use @import。 一般css我们讲究早加载,与dom一起渲染,避免dom重绘。而@import的引入方法会导致css迟加载,导致页面先渲染了没有css的dom,然后重绘更新,用户体验比较差。
- 首屏服务端直出。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的展示。
多页应用拆解公共部分长久缓存,多个页面可以只加载一次公共部分,或者业务变化时保持了公共部分不加载