前端优化
浏览器发送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
标签的祖先节点,效率并不像之前想象的那么高。根据浏览器的这一行为特点,在写选择符的时候需要注意很多事项。