前端资源很多, 包括 html,CSS,JavaScript,Image,Flash 等资源. 前端优化是复杂的, 有很多细分的优化方式, 很多人都是大致了解一部分, 但具体怎么优化却好无头绪.
首先, 我们先要弄明白, 前端优化的目的是什么?
1, 从用户角度来说, 优化可以让页面加载速度更快, 对用户的操作响应更及时, 减少等待时间, 提升用户体验.
2, 从服务商角度来说, 优化能够减少页面请求数, 或减小请求所占带宽, 能够节省可观的资源.
前端优化的途径按粒度可大致分为两类.
一, 页面级优化
1, 减少 HTTP 请求数
2, 避免重复的资源请求
3, 减少 cookie 传输
4,Lazy Load
5, 将 CSS 放在 HEAD 中
6, 将外部脚本置底
7, 减少重定向
8, 异步请求 Callback
9, 使用 CDN
10, 使用反向代理
二, 代码级优化
1, 减少 DOM 操作
2, 慎用 with
3, 避免使用 eval 和 Function
4, 减少作用域链查找
5, 数据访问
6, 字符串拼接
7,CSS 选择符优化
一, 页面级优化
1, 减少 HTTP 请求数
(1) 从设计实现层面简化页面
如果你的页面像百度首页一样简单, 那么接下来的规则基本上都用不着了. 保持页面简洁, 减少资源的使用时最直接的. 如果不是这样, 你的页面需要华丽的皮肤, 则继续阅读下面的内容.
(2) 合理设置 HTTP 缓存
适当的缓存设置可以大大减少 HTTP 请求.(这里需要说明的是, 如果直接 F5 刷新页面的话效果是不一样的, 这种情况下请求数还是一样, 不过被缓存资源的请求服务器是 304 响应, 只有 Header 没有 Body , 可以节省带宽)
设置原则很简单: 能缓存越多越好, 能缓存越久越好. 例如, 很少变化的图片资源可以直接通过 HTTP Header 中的 Expires 设置一个很长的过期头 ; 变化不频繁而又可能会变的资源可以使用 Last-Modifed 来做请求验证. 尽可能的让资源能够在缓存中待得更久.
!!! 此处着重了解 HTTP 缓存的具体设置和原理!!!
(3) 资源合并与压缩
现在提供了很多打包工具可以压缩 CSS,JavaScript,Image, 压缩后往往能省下不少空间. 打包工具有 webpack,gulp,grunt...
(4)CSS Sprites
合并 CSS 图片, 可以减少请求数. 可使用雪碧图, 把所有相对较小的资源图片, 绘制在一张大图上, 只需要将大图下载下来, 然后利用图片定位来讲小图展现在页面中 (background-position: 百分比, 数值)
(5)Inline Images
使用 data:URL scheme 的方式将图片嵌入到页面或 CSS 中, 如果不考虑资源管理上的问题的话, 不失为一个好办法. 如果是嵌入页面的话换来的是增大了页面的体积, 而且无法利用浏览器缓存. 使用在 CSS 中的图片则更为理想一些.
2, 避免重复的资源请求
在团队开发一个项目时, 由于不同开发者之间都可能会向页面中添加页面或组件, 因此可能相同的脚本会被添加多次.
重复的脚本会造成不必要的 HTTP 请求 (如果没有缓存该脚本的话), 并且执行多余的 JavaScript 浪费时间, 还有可能造成错误.
如何避免重复脚本呢?
(1) 形成良好的脚本组织. 重复脚本有可能出现在不同的脚本包含同一段脚本的情况, 有些是必要的, 但有些却不是必要的, 所以需要对脚本进行一个良好的组织.
(2) 实现脚本管理器模块.
3, 减少 cookie 传输
一方面, cookie 包含在每次请求和响应中, 太大的 cookie 会严重影响数据传输, 因此哪些数据需要写入 cookie 需要慎重考虑, 尽量减少 cookie 中传输的数据量. 另一方面, 对于某些静态资源的访问, 如 CSS,script 等, 发送 cookie 没有意义, 可以考虑静态资源使用独立域名访问, 避免请求静态资源时发送 cookie, 减少 cookie 传输次数.
4,LazyLoad Images
这条策略实际上并不一定能减少 HTTP 请求数, 但是却能在某些条件下或者页面刚加载时减少 HTTP 请求数. 对于图片而言, 在页面刚加载的时候可以只加载第一屏, 当用户继续往后滚屏的时候才加载后续的图片. 这样一来, 假如用户只对第一屏的内容感兴趣时, 那剩余的图片请求就都节省了.
5, 将 CSS 放在 HEAD 中
将样式表放在头部对于实际页面加载的时间并不能造成太大影响, 但是这会减少页面首屏出现的时间, 使页面内容逐步呈现, 改善用户体验, 防止 "白屏".
6, 将外部脚本置底
JS 的下载和执行会阻塞 Dom 树的构建 (严谨地说是中断了 Dom 树的更新), 也会会阻塞其他资源, 例如在脚本加载完成之前, 它后面的图片, 样式以及其他脚本都处于阻塞状态, 直到脚本加载完成后才会开始加载. 所以 script 标签放在首屏范围内的 HTML 代码段里会截断首屏的内容.
7, 减少重定向
什么是重定向?
重定向用于将用户从一个 URL 重新路由到另一个 URL.
常用重定向的类型
301: 永久重定向, 主要用于当网站的域名发生变更之后, 告诉搜索引擎域名已经变更了, 应该把旧域名的的数据和链接数转移到新域名下, 从而不会让网站的排名因域名变更而受到影响.
302: 临时重定向, 主要实现 post 请求后告知浏览器转移到新的 URL.
304:Not Modified, 主要用于当浏览器在其缓存中保留了组件的一个副本, 同时组件已经过期了, 这是浏览器就会生成一个条件 GET 请求, 如果服务器的组件并没有修改过, 则会返回 304 状态码, 同时不携带主体, 告知浏览器可以重用这个副本, 减少响应大小.
重定向如何损伤性能?
当页面发生了重定向, 就会延迟整个 HTML 文档的传输. 在 HTML 文档到达之前, 页面中不会呈现任何东西, 也没有任何组件会被下载.
来看一个实际例子: 对于 ASP.NET webform 开发来说, 对于新手很容易犯一个错误, 就是把页面的连接写成服务器控件后台代码里, 例如用一个 Button 控件, 在它的后台 click 事件中写上: Response.Redirect(""); 然而这个 Button 的作用只是转移 URL, 这是非常低效的做法, 因为点击 Button 后, 先发送一个 Post 请求给服务器, 服务器处理 Response.Redirect("") 后就发送一个 302 响应给浏览器, 浏览器再根据响应的 URL 发送 GET 请求. 正确的做法应该是在 HTML 页面直接使用 a 标签做链接, 这样就避免了多余的 post 和重定向.
重定向的应用场景
(1) 跟踪内部流量
重定向经常用于跟踪用户流量的方向, 当拥有一个门户主页的时候, 同时想对用户离开主页后的流量进行跟踪, 这时可以使用重定向. 例如: 某网站主页新闻的链接地址 http://a.com/r/news, 点击该链接将产生 301 响应, 其 Location 被设置为 http://news.a.com. 通过分析 a.com 的 Web 服务器日志可以得知人们离开首页之后的去向.
我们知道重定向是如何损伤性能的, 为了实现更好的效率, 可以使用 Referer 日志来跟踪内部流量去向. 每个 HTTP 请求都有一个 Referer 表示原始请求页 (除了从书签打开或直接键入 URL 等操作), 记录下每个请求的 Referer, 就避免了向用户发送重定向, 从而改善了响应时间.
(2) 跟踪出站流量
有时链接可能将用户带离你的网站, 在这种情况下, 使用 Referer 就不太现实了.
同样也可以使用重定向来解决跟踪出站流量问题.
除了重定向外, 我们还可以选择使用信标 (beacon)-- 一个 HTTP 请求, 其 URL 中包含有跟踪信息. 跟踪信息可以从信标 Web 服务器的访问日记中提取出来, 信标通常是一个 1px*1px 的透明图片, 不过 204 响应更优秀, 因为它更小, 从来不被缓存, 而且绝不会改变浏览器的状态.
8, 异步请求 Callback
在某些页面中可能存在这样一种需求, 需要使用 script 标签来异步的请求数据. 类似:
像以上这种方式直接在页面上写 script 对页面的性能也是有影响的, 由于浏览器在页面处理方面是单线程的, 当 inline 脚本在页面渲染之前执行时, 页面的渲染工作则会被推迟, 即增加了页面首次加载的负担, 推迟了 DOMLoaded 和 Windows.onload 事件的触发时机. 简而言之, inline 脚本在执行的时候, 页面处于空白状态. 鉴于以上两点原因, 如果时效性允许的话, 建议将执行时间较长的 inline 脚本异步执行, 异步的方式有很多种, 例如可以考虑在 DOMLoaded 事件触发的时候加载, 或者使用 setTimeout 方式来灵活的控制加载的时机, 或者使用 script 元素的 defer 属性 (存在兼容性问题和其他一些问题, 例如不能使用 document.write). 此外, 在 HTML5 中引入了 Web Workers 的机制, 恰恰可以解决此类问题.
9, 使用 CDN
CDN(内容发布网络) 是一组分布在多个不同地理位置的 Web 服务器, 用于更加有效地向用户发布内容. 在优化性能时, 向特定用户发布内容的服务器的选择基于对网络慕课拥堵的测量. 例如, CDN 可能选择网络阶跃数最小的服务器, 或者具有最短响应时间的服务器.
CDN 还可以进行数据备份, 扩展存储能力, 进行缓存, 同时有助于缓和 Web 流量峰值压力.
如果应用程序 Web 服务器离用户更近, 那么一个 HTTP 请求的响应时间将缩短. 另一方面, 如果组件 Web 服务器离用户更近, 则多个 HTTP 请求的响应时间将缩短.
CDN 的缺点:
1, 响应时间可能会受到其他网站流量的影响. CDN 服务提供商在其所有客户之间共享 Web 服务器组.
2, 如果 CDN 服务质量下降了, 那么你的工作质量也将下降
3, 无法直接控制组件服务器
10, 使用反向代理
传统代理服务器位于浏览器一侧, 代理浏览器将 http 请求发送到互联网上, 而反向代理服务器位于网站机房一侧, 代理网站 Web 服务器接收 http 请求. 如下图所示:(pic1.PNG)
论坛网站, 把热门词条, 帖子, 博客缓存在反向代理服务器上加速用户访问速度, 当这些动态内容有变化时, 通过内部通知机制通知反向代理缓存失效, 反向代理会重新加载最新的动态内容再次缓存起来.
此外, 反向代理也可以实现负载均衡的功能, 而通过负载均衡构建的应用集群可以提高系统总体处理能力, 进而改善网站高并发情况下的性能.
二, 代码级优化
1, 减少 DOM 操作
a.HTML Collection(HTML 收集器, 返回的是一个数组内容信息)
在脚本中 document.images,document.forms,getElementsByTagName() 返回的都是 HTMLCollection 类型的集合, 在平时使用的时候大多将它作为数组来使用, 因为它有 length 属性, 也可以使用索引访问每一个元素. 不过在访问性能上则比数组要差很多, 原因是这个集合并不是一个静态的结果, 它表示的仅仅是一个特定的查询, 每次访问该集合时都会重新执行这个查询从而更新查询结果. 所谓的 "访问集合" 包括读取集合的 length 属性, 访问集合中的元素.
因此, 当你需要遍历 HTML Collection 的时候, 尽量将它转为数组后再访问, 以提高性能. 即使不转换为数组, 也请尽可能少的访问它, 例如在遍历的时候可以将 length 属性, 成员保存到局部变量后再使用局部变量.
b. Reflow & Repaint
除了上面一点之外, DOM 操作还需要考虑浏览器的 Reflow 和 Repaint , 因为这些都是需要消耗资源的.
2, 慎用 with
with(obj){ p = 1}; 代码块的行为实际上是修改了代码块中的执行环境 , 将 obj 放在了其作用域链的最前端, 在 with 代码块中访问非局部变量是都是先从 obj 上开始查找, 如果没有再依次按作用域链向上查找, 因此使用 with 相当于增加了作用域链长度. 而每次查找作用域链都是要消耗时间的, 过长的作用域链会导致查找性能下降.
因此, 除非你能肯定在 with 代码中只访问 obj 中的属性, 否则慎用 with, 替代的可以使用局部变量缓存需要访问的属性.
3, 避免使用 eval 和 Function
每次 eval 或 Function 构造函数作用于字符串表示的源代码时, 脚本引擎都需要将源代码转换成可执行代码. 这是很消耗资源的操作 -- 通常比简单的函数调用慢 100 倍以上.
eval 函数效率特别低, 由于事先无法知晓传给 eval 的字符串中的内容, eval 在其上下文中解释要处理的代码, 也就是说编译器无法优化上下文, 因此只能有浏览器在运行时解释代码. 这对性能影响很大.
Function 构造函数比 eval 略好, 因为使用此代码不会影响周围代码 ; 但其速度仍很慢.
此外, 使用 eval 和 Function 也不利于 JavaScript 压缩工具执行压缩.
4, 减少作用域链查找
如果在循环中需要访问非本作用域下的变量时请在遍历之前用局部变量缓存该变量, 并在遍历结束后再重写那个变量, 这一点对全局变量尤其重要, 因为全局变量处于作用域链的最顶端, 访问时的查找次数是最多的.
低效率的写法:
更高效的写法:
此外, 要减少作用域链查找还应该减少闭包的使用.
5, 数据访问
JavaScript 中的数据访问包括直接量 (字符串, 正则表达式 ), 变量, 对象属性以及数组, 其中对直接量和局部变量的访问是最快的, 对对象属性以及数组的访问需要更大的开销. 当出现以下情况时, 建议将数据放入局部变量:
a. 对任何对象属性的访问超过 1 次
b. 对任何数组成员的访问次数超过 1 次
另外, 还应当尽可能的减少对对象以及数组深度查找.
6, 字符串拼接
在 JavaScript 中使用 "+" 号来拼接字符串效率是比较低的, 因为每次运行都会开辟新的内存并生成新的字符串变量, 然后将拼接结果赋值给新变量. 与之相比更为高效的做法是使用数组的 join 方法, 即将需要拼接的字符串放在数组中最后调用其 join 方法得到结果. 不过由于使用数组也有一定的开销, 因此当需要拼接的字符串较多的时候可以考虑用此方法.
7,CSS 选择符优化
在大多数人的观念中, 都觉得浏览器对 CSS 选择符的解析式从左往右进行的, 例如
#toc A { color: #444; } 这样一个选择符, 如果是从右往左解析则效率会很高, 因为第一个 ID 选择基本上就把查找的范围限定了, 但实际上浏览器对选择符的解析是从右往左进行的. 如上面的选择符, 浏览器必须遍历查找每一个 A 标签的祖先节点, 效率并不像之前想象的那样高. 根据浏览器的这一行为特点, 在写选择符的时候需要注意很多事项, 有兴趣的童鞋可以去了解一下.
来源: http://www.jianshu.com/p/687bca2f8617