前端优化文档
前端优化文档
链接: 前端优化
前端优化
浏览器发送HTTP请求,服务器收到请求全文后,返回HTTP响应,在浏览器接收之后结束这个过程。浏览器和服务器只有一次互动的机会,浏览器主动发起,而服务器被动地根据收到的请求内容返回结果。一个完整的请求都需要经过DNS寻址、与服务器建立连接、发送数据、等待服务器响应、接收数据的过程。
前端优化的途径
- 页面级别 的优化,例如HTTP请求数、脚本的无阻塞加载、内联脚本的位置优化等;
- 代码级别 的优化,例如JavaScript中的DOM操作优化、CSS选择符优化、图片优化以及HTML结构优化等。
页面级优化
1. 减少HTTP请求数
减少HTTP请求数的主要途径
1、从设计实现层面简化页面
保持页面简洁、减少资源的使用 是最直接的。能使用CSS替代效果就尽量少使用图片。
2、合理设置HTTP缓存
恰当地缓存设置可以大大减少HTTP请求。被缓存资源的请求服务器是304响应,只有 Header 没有 body ,没有节省带宽。对于多个页面都可能使用到的代码,尽量拆分到同一个文件中。如果是嵌入页面换来的是增大了页面的体积,而且无法利用浏览器缓存。
3、资源合并和压缩
如果可以,尽可能将外部的脚本、样式进行合并,多个合为一个。另外,CSS、JavaScript、image都可以用相应的工具进行压缩。
4、 CSS Sprites
合并CSS图片,减少请求数的有一个好办法。
5、 lazy load image
这个策略实际上并不一定能减少HTTP请求数,但是却能在某些条件下或者页面刚加载时减少HTTP请求数。对于图片而言,在页面刚加载时可以只加载第一屏,当用户继续往后滚屏时才加载后续的图片。以前的做法是在加载时把第一屏之后的图片地址缓存在 textarea 标签中,待用户往下滚屏时才 惰性 加载。百度图片和花瓣网也是用这种流行的瀑布流加载图片。
2. 将外部脚本置底
外链脚本在加载时会阻塞其他资源 ,例如在脚本加载完成之前,它后面的图片、样式以及其他脚本都处于阻塞状态,直到脚本加载完成后才会开始加载。如果把脚本放在比较靠前的位置,则会影响整个页面的加载速度从而影响用户体验。最简单可依赖的方法是将脚本尽可能往后挪,减少对并发下载的影响。如果时效性允许的话,可以考虑在 DOMLoaded 事件触发时加载,或者用 setTimeout 方式来灵活控制加载的时机。
3. 异步执行 inline 脚本
inline 脚本对性能的影响比外部脚本大很多。首先,与外部脚本一样, inline 脚本在执行时也会阻塞并发请求,除此之外,由于浏览器在页面处理方面时单线程的,当 inline 脚本在页面渲染之前执行时,页面的渲染工作则会被推迟。简而言之, inline 脚本在执行时页面处于空白状态。
鉴于以上两点,建议将 **执行时间较长的inline 脚本异步执行 ** 。异步执行的方式有很多种,例如使用 script 元素的 defer 属性、使用 setTimeout ,此外,在HTML5中引入了 web workers 的机制,恰恰可以解决此类问题。
4. lazy load JavaScript
目前的做法大概有两种,一种是为流量特别大的页面专门定制一个专用的 mini 版框架,另一种则是 lazy load ,最初只加载核心模块,其他模块可以等到需要使用的时候才加载,类似于 java 的 swing ,引入需要的组件库文件。
5. 将CSS放在 head 中
6. 减少不必要的HTTP跳转
对于以目录形式访问的HTTP链接,很多人都会忽略链接最后是否带 / ,假如服务器对此区别对待,那么其中很可能隐藏了301跳转,增加了多余请求。
代码级优化
1. JavaScript
1、 DOM
DOM操作应该是脚本中最耗性能的一类操作,例如增、删、查、改DOM元素或者对DOM集合进行操作。如果脚本中包含了大量的DOM操作则需要注意 html collection 。
在脚本中 document.images 、 document.forms 、 getElementsByTagName() 返回的都是 HTMLCollection 类型的集合,在平时使用的时候大多将它作为数组来使用,因为它有 length 属性,也可以使用索引访问每一个元素。不过在访问性能上则比数组要差很多,原因这个集合并不是一个静态的结果,它表示的仅仅是一个特定的查询,每次访问该集合时都会重新执行这个查询从而更新查询结果。所谓的 访问集合 包括读取集合的 length 属性、访问集合中的元素。
因此,当你需要遍历 HTML collection 时,尽量将它 转为数组后再访问 ,以提高性能。即使不转换为数组,也请尽可能少地访问它,例如在遍历的时候可以将 length 属性、成员保存到局部变量后再使用局部变量。
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中的数据访问包括直接量(字符串、正则表达式)、变量、对象属性以及数组,其中对直接量和局部变量的访问是最快的,对对象属性以及数组的访问需要更大的开销。当出现以下情况时,建议将数据放入局部变量:
- 对任何 对象属性 的访问超过1次
- 对任何 数组成员 的访问次数超过1次
另外,还应当尽可能的 减少对对象以及数组深度查找 。
6、字符串拼接
在JavaScript中使用 + 号来拼接字符串效率是比较低的,因为每次运行都会开辟新的内存并生成新的字符串变量,然后拼接结果赋值给新变量。之前使用jQuery+Ajax交互页面,很多时候都是将后台传输过来的数据和前端 HTML 结构拼接成字符串,然后呈现在页面HTML容器里。
与之相比更为高效的做法是 **使用数组的join 方法 ** ,即将需要拼接的字符串放在数组中最后调用其 join 方法得到结果。不过由于使用数组也有一定的开销,因此当需要拼接的字符串较多时可以考虑使用此方法。
2. CSS选择符
在大多数人的观念中,都觉得浏览器对CSS选择符的解析是从左往右进行的。
如果是从右往左解析则效率会很高,因为第一个ID选择基本上就把查找的范围限定了,但实际上浏览器对选择符的解析是从右往左进行的。 #tag A {color: "#ccc";} ,浏览器必须遍历查找每一个 A 标签的祖先节点,效率并不像之前想象的那么高。根据浏览器的这一行为特点,在写选择符的时候需要注意很多事项。