网站性能优化的那些事儿

关于前端优化,前辈们的挖掘从未停止,本文站在巨人的肩膀上,做个整理笔记。

常识

终端用户80%到90%的响应时间都花在了下载页面组件上:图片,样式,脚本,Flash等等,这是 业绩黄金法则 。所以尽可能的减少下载时间,是网站优化的首要任务。

  • 压缩 JavaScript 和 CSS

  • 把 JavaScript 和 CSS 放到外面

  • 将样式表放在文档的顶部,将 JavaScript 放在底部
    也就是所有的样式放在<head>中,所有的脚本放到最下边的</body>之前。这真的是一条最最基本的规则了,以前只知道别人这么写比较标准,殊不知标准是有标准的道理的。
    因为浏览器总是试图渐进式的渲染页面;它们想按元素到达的时间顺序的进行渲染。而我们打开网页时最想第一时间看到它的页面,所以,将css放到前面最先渲染出来,以至于网站不那么丑也就不足为奇了。
    将js放到底部是因为,浏览器在处理脚本时会中断渲染,所以,要使页面被尽可能快的渲染,将styles放在顶部。为了阻止JS的阻塞影响到渲染,将scripts放在底部。

  • 尽可能减少 HTTP 请求
    这也就是为什么我们要尽可能的把网站的小图标放到一张图上,否则每个小图标都要进行一次 http 请求,而每一次 http 请求都可能引发 DNS 查询,重定向,404,等等。不要小看这些,这可都是需要时间的。
    所以合并css、js、images是前端可以做的最快的优化方法之一。

  • 从多个域名提供资源服务能增加浏览器并行下载的资源数量。
    简单的说,浏览器会从一个单一的域名并行的尽可能多的下载资源。它从越多的域名下载,就能在一瞬间并行的获得更多的资源。比如,浏览器只能一次从一个域名获取两个资源,那么由两个域名提供服务意味着它可以一次性获取四个资源;三个域名意味着六个并行下载。当然,若再加上实际的CDN技术就会更好,CDN技术通过从一个更加合适的物理位置提供资源服务的方法来减少延迟。

首屏耗时优化

说起前端性能优化,在谈论的内容,无非是:加载性能、渲染速度、用户交互响应速度、动画流畅性、DOM操作无闪动…..但重中之重的还是首屏时间,首屏时间就是给用户的第一印象,这个 1 没有做好,后面再多的 0 ,也留不住用户。

  • localStorage 缓存数据优先展示
    加快首屏数据展示

  • jsonp 预拉取数据
    减少二次渲染,加快首屏 cgi 数据显示

  • 按需延时加载非必须资源
    减少首屏资源体积

  • 通用数据/图片内容共享
    加快可通用部分数据渲染

  • 跨 webview 预拉取数据
    利用 webview 创建时间加载首屏数据,加快首屏数据显示

  • 图片懒加载
    对于 图片/视频 多的网站,使用lazy load,延迟加载。比如在很多电商平台,下拉页面时可以看到图片上的loading标志就是用lazy load原理啦。

    • 模板渲染时,不赋值src
    • 插入DOM树时进行可视区域计算
    • 如果图片在可视区内,赋值src
    • 如果图片在可视区外,留着scroll处理

css 优化

  • 避免使用 CSS 表达式

  • 避免使用滤镜

js 优化

  • 去除重复脚本

  • js尽量减少 DOM 访问

  • 用智能的事件处理器

图片优化

  • 优化图片

  • 优化 CSS Sprite

  • 不要用HTML缩放图片

  • 用小的可缓存的 favicon.ico(P.S. 收藏夹图标)

内容优化

  • 尽量减少HTTP请求数
    常用方法:合并文件、CSS Sprites图像映射

  • 减少DNS查找

  • 避免重定向

  • 让Ajax可缓存

  • 延迟加载组件

  • 预加载组件

  • 减少DOM元素的数量

  • 跨域分离组件

  • 尽量少用iframe

  • 杜绝404

服务器端优化

  • 使用CDN(Content Delivery Network)
    对刚刚起步的公司和个人网站来说,CDN服务的成本是很高的(需要money),但如果你的用户群越来越大,越来越全球化,那么用CDN来换取更快的响应时间还是很有必要的。

  • 添上Expires或者Cache-Control HTTP头

  • Gzip组件

  • 配置ETags

  • 尽早清空缓冲区

  • 对Ajax用GET请求

  • 避免图片src属性为空

原理解释

  • HTTP请求与DNS 查询
    每当你从任何域名请求一个资源,会发出一个带有相关头部,被访问资源的 HTTP请求,并且会返回一个响应。这是对该过程的一个极端简化,但它基本就是事实上你需要知道的。这是一个HTTP请求,而且所有涉及的资源都从属于这个往返的旅行。当提到前端性能,这些请求正是主要的瓶颈所在,因为如我们谈到的,浏览器受限于有多少请求可以并行发生。这也是为什么我们经常要使用子域名;以便允许这些请求在数个域名上发生,允许同时发生多得多数量的请求。
    然而关于这还有个问题,DNS查询。每次(从一个空缓存)一个新的域名被引用,HTTP请求会受制于一个耗时的DNS查询(某个介于20到120毫秒之间的值),在DNS查询中,发出的请求会查询资源实际存在的地点;互联网通过IP地址被绑定在一起,这些地址由DNS管理的主机名引用。
    如果每个引用的新域名具有DNS查询的前端代价,你必须确保这个代价确实是值得的。如果是一个小网站(例如像CSS魔法),那么由子域名提供资源可能并不值得;相比执行多个域名的DNS查询并将其并行化来说,从一个域名非并行的获取若干资源,浏览器可能更快。
    如果你或许有一打资源,你可能会考虑从一个子域名提供它们的资源服务;为了更好的并行化那许多资源,额外的DNS查询可能是值得的。如果说你有40个资源,可能将那些资源切分到两个子域名是值得的;为了由总数为三个的域名提供你的网站服务,两个额外的DNS查询会是值得的。
    DNS查询代价很高,因此你需要决定什么才是对你的网站更合适的;承担查询的消耗或者只是由一个域名提供所有服务。
    很重要的需要记得的是,比方说一旦HTML被请求于foo.com,对那个主机的DNS查询就立即发生了,所以后续的任何对foo.com的请求不再受制于DNS查询。

  • DNS预取
    DNS预取所做的恰恰就是凭证领餐(on the tin),它不能被简单实现。比方说,如果你需要请求来自widget.foo.com的资源,那么你可以通过简单的在页面的里先增加下面这个来预取那个主机的DNS:

    1
    2
    3
    4
    5
    <head>
    ...
    <link rel="dns-prefetch" href="//widget.foo.com">
    ...
    </head>

    这行简单的内容将会告诉支持的浏览器去开始预取那个域名的DNS,这要稍稍早于它实际需要的时刻。它意味着DNS查询过程,在浏览器<script>元素真正请求小程序的时候就已经在进行中了。
    关于prefetch的更多介绍戳这里

  • CDN
    内容分发网络(CDN)是一组分散在不同地理位置的web服务器,用来给用户更高效地发送内容。典型地,选择用来发送内容的服务器是基于网络距离的衡量标准的。例如:选跳数(hop)最少的或者响应时间最快的服务器。

参考

Front-end performance for web designers and front-end developers
雅虎军规
AC2015前端技术大会