这是早期总结下来的一些项目优化经验,现在整理成文。或者有些地方已经过时或是不准确,欢迎指出。
- “web性能优化”是个系统化工程,数据、资源存储=>网络传输=>页面展示,系统架构、代码、资源,每个层面都有可深入优化的地方。网上关于这方面的文献也挺多,流传最多的可算是雅虎网站页面性能优化的34条黄金守则。然而只有能适合具体业务环境的、花费更小的成本实现更大优化的,才是有效的优化。下面我想结合自己平时所接触的业务,从身边能够着手操作的地方,抛砖引玉也说几点。
从“瘦身”这个件事说起
- 现在的个人电子设备配置已经很高了,硬件运行效率不再成为限制。web的主要性能瓶颈在于网络带宽。减少要传输的资源的体积,是web优化最重要的事情,没有之一。
代码压缩
- css压缩、js压缩,前端自动化工具如gulp、grunt已经提供了成熟的压缩技术方案。又如模块化打包工具webpack,提供了一系列“黑魔法”可以设置优化(optimization),不只是压缩,还能针对单页面、多页面应用优化方案来灵活设置。css只能去回车、空格、注释,压缩率不算太高,但js能混淆变量,压缩率能达到50%左右。
- html压缩存在着争议。虽然也属于能减少体积的手段,然而在经过gzip压缩后,html本身是否有压缩体积区别不大。而且html压缩还会带来一些问题,如回车、空格后,在页面渲染时会有一个字符的间隙,压缩后因为回车被去除这个间隙也没有了,给开发造成不便。
图片压缩
- 页面中图片占比最大,对图片进行压缩很有必要。在线压缩png的网站,特别推荐熊猫(有兴趣的同学可以研究一下TinyPNG的压缩算法),而腾讯的智图支持多种格式图片的压缩。
gzip压缩
浏览器发送请求的时候,请求头会带上Accept-Encoding(浏览器告诉服务器自己支持什么编码)。
gzip是主流浏览器普遍支持的压缩编码,而sdch是chrome推广的压缩编码。服务器知道后就返回对应的编码内容。而gzip能设置压缩比率,越高压缩率就越高但消耗cpu资源,压缩后能比原文件减少40%~80%的体积,压缩率非常可观。但gzip对媒体文件(图片、视频)的压缩效果不明显,所以一般只对css、js、html格式资源开启。
- 做好了资源“瘦身”之后,其实优化已进行了60%了,这是低成本换取大成效的优化,在平时开发中应该是必备手段。
从网络传输层入手
- 在浏览器输入一个地址,然后按下回车,就会一系列网络传输事情:域名解析、TCP三次握手,浏览器发起http请求,服务器响应,浏览器解析html代码,请求静态资源,浏览器渲染页面。浏览器本身对一些环节也内置了优化措施,如DNS缓存、本地资源缓存等,对其熟悉的开发者可以从当中环节入手进行优化。
200 OK (from cache) 、304 Not Modified
这是浏览器使用缓存时的状态码,但区别是前者是浏览器没有对服务器发出真实的请求,后者是有向服务器确认过后再使用缓存。
12345678910// 请求头里包含了If-Modified-Since:Thu, 12 May 2016 01:39:48 GMTIf-None-Match:"ALZ8LAH1tVxaNHjkdcovaHQok277"//响应头里包含了ETag:"ALZ8LAH1tVxaNHjkdcovaHQok277"Last-Modified:Thu, 12 May 2016 01:39:48 GMT两者一致时,代表服务器上的资源没修改过,浏览器可使用缓存。
响应头中包含了Cache-Control信息,表明了资源可缓存时间,请求的资源在此时间内,就会出现200 ok(from cache)的状态。但当用户刷新浏览器时,还是304状态,需要向服务器重新确认。
12Cache-Control:public, max-age=31536000利用浏览器缓存,能有效减少请求、网络带宽的开销。
减少请求
- 每个请求经过的步骤复杂,携带的多余信息也多,减少请求能降低服务器压力,减少消耗在DNS解析的时间,减少带宽消耗。
- 页面中使用外联js或css时,先进行合并、压缩处理。
- 多个icon或者小图片的资源,可以制作成雪碧图(CSS Sprites)。
- 单个小图片使用内嵌资源来引入,如:,webpack通过设置url-loader可把少于一定体积的图片自动转换成base64。
- 若使用ajax请求页面信息,尽量把多个零碎的请求合并成单个请求。
- 避免在页面脚本中做重定向,这等于浪费了一轮请求。
利用html5特性进行资源缓存
- html5提供了appcache和localstorage,能方便对静态资源、数据进行缓存。
appcache:
- 应用缓存,在首次加载完资源后,再次请求该资源时可以直接使用缓存。目前已在项目中大量使用。但本身还是有几个坑,比如修改资源后用户要2次载入页面才能看到最新内容。又如会自动把使用appcache的页面html缓存,所以在多页项目中不适合使用appcache。
localstorage:
- 本地存储,以键值对的形式把数据存储在本地,主流浏览器给每个域提供5mb左右容量。适用的场景很多。例如:
- 可对更新不频繁的,但用户会多次浏览的数据进行缓存,减少重复请求。
- 能代替部分cookie的功能,如存储用户信息。
- 把静态资源(css/js)缓存进localstorage。这种做法能解决远程资源加载问题,而且当单一域下有多个单独的webapp要用同一套js、css时,都能从本地直接读取。但这种做法带来了其他问题,如版本维护、脚本安全性、脚本执行效率。具体还待实践。
DNS 预解析缓存
- 例:
浏览器会提前对href下的域名进行 DNS 解析并且缓存,而不会在需要请求资源再进行解析。在一些大站中,为了并发加载资源而采取多域名cdn策略,DNS预解析效果就明显了。多域名策略
- http请求会把当前域的cookie默认带上,但cookie对于css、js、图片资源是用处很小的,那么这样就会造成带宽浪费了。把一些资源放在别的域上,可以让http请求头减重。
- http协议限制了单个域名的并发下载数,把要加载的资源分布在多个域名下,能提高并发。
CDN加速
- 内容分发网络,能让用户就近取得所需内容,提高响应速度、解决网络延迟问题。目前已有很多cdn服务商能提供优质服务。
- 网络传输层面的优化也是web优化的一个大头,但是其实现成本较高,如cdn服务就需要购买,利用html5缓存就可能要涉及到架构上的改动,与承担新技术带来的风险。TCP优化、集群等,我还没接触过,欢迎补充。
从代码层面优化
- 浏览器把所需的资源加载回来后,还需要进行解析源码、渲染页面。代码质量带来的影响会随着代码的规模越来越明显。我是一名前端开发者,所以只从前端代码说起。
html
- css、js使用外联方式引入,css在head时候就引入,js在html内容末尾引入。这样的做法能是html体积减少,并能使浏览器更快的取得css渲染页面,避免了因为加载和解析js而造成页面堵塞。
- 减少标签嵌套深度。
- 尽量少使用iframe。
css
- 合理使用选择器。浏览器是从右向左解析css的选择器的,如: .a-block p.b-block a, 浏览器会查找所有a元素,查找带有.b-block类的父元素,查找这些父元素具有p标签的元素, 再往上匹配是否带有.a-block类的父元素。相比之下,.a-block .b-block .link这样的效率会稍好。
- 减少选择器嵌套深度。用less、sass这种预处理器,很容易就造成嵌套太深的情况,嵌套5、6层的情况常有。嵌套太深,会增加浏览器匹配查找的次数,应该控制在3层以内。
- 给图片写定宽高,避免图片加载完成后,图片本身的宽高撑开页面内容,造成了部分页面重绘。
js
小心操作DOM。DOM元素包含了数据、节点、事件等信息,是很重的,频繁操作的DOM的代价很高。比如:
每次循环中就要执行一次DOM操作,document.getElementById(‘list’).getElementsByTagName(‘li’),并获取其length值,在执行中还要操作一次DOM,改变其样式。改成以下会更优:
首先就把获取到的DOM元素缓存,在for循环内先把DOM集合的length值缓存,在循环体内通过给DOM元素加上类的方式,实现改变样式的结果,而且还能应对有多个样式同时要改变的场景。避免了多次操作DOM。
- 使用documentFragment(文档片段)对页面大块内容的更改。每次文档插入都是一次开销,并可能是页面重绘,当有大块文档需要插入时,较优的做法是创建documentFragment对象,再对此对象进行复杂的操作,最后才一次性插入到文档。js库vuejs也采用了这种做法。
- 使用事件代理方式来给元素绑定事件
绑定事件会占用处理时间与内存。利用事件冒泡机制,只在父元素上绑定事件,然后判断事件源,就能处理子元素的事件了。jquery等一些流行库已在绑定事件方法的底层封装好这种方式。 - 操作页面元素时,要尽量避免发生重绘。
代码质量是个持续优化的过程,应该在开发意识中提升。
“用户感知”性能
- web优化的最终目的是能让用户更快地看到页面内容。然而除了让页面更快地加载之外,还有其他方式能让用户更快看到页面内容。
懒加载
- 以内容主导的站点很多采用了懒加载。当页面内容过多的时候,首次只加载用户能看到的第一屏的内容,当用户继续往下浏览时,再按需加载更多内容。这样无需把整个页面都加载完就能让用户更快地看到内容。使用的场景如图片、长列表、历史消息。
预加载
- 漫画网站、小说网站中,用户在浏览当页中的内容过程中,下一页的内容已经在加载了,在用户点击下一页时,就能立刻看到内容。
避免资源浪费
- 在移动端上,各种设备的屏幕大小不一,在小屏上使用一个大分率的图片没必要。其实在小屏设备上用更小分辨率的图片,在用户看来都是一样。可以通过js判断当前设备屏宽引入不同的图片url,或者css3的媒体查询也可实现。
让“等待”更有趣
- 当请求需要的时间比较长时,提供一些交互元素让用户的等待更有趣,那么用户会原谅页面加载的慢,页面加载也会显得更流畅,尽管费时可能更多。详见知乎。
这部分就有点涉及用户体验方面的优化了,需要与设计、交互一起探讨方案。
最后提出的一点是,优化的时机要合适。项目开始前可以布置资源缓存优化的基础建设,前期还需要根据业务需求设计好交互、体验上的优化。项目进行中就应该进行代码级的优化,如果到了后期再回头优化代码就伤筋动骨了。项目发布前,就要进行资源压缩。高成本的优化要等项目上线一定时间后再视乎情况而进行,避免过早优化,花费大力气去优化一个不显眼的地方。