首页 > 优化技术 > 正文

如何提升前端性能优化

前端开发中的代码精简、易维护性、兼容性处理是至关重要的议题。从实际工程应用的角度审视,最常见的前端性能优化难题。前端性能优化的基本准则,大致包含了当前前端领域的大部分优化原则,众多高阶且细致的优化策略均源于这些基础原则。

前端性能优化规则有哪些

1. 降低HTTP请求频率

尽可能合并图片、CSS、JS文件。例如,一个页面包含5个CSS文件,将其合并为一个文件,则只需发送一次HTTP请求,从而节省网络请求时间,加速页面加载。

2. 采用CDN

网站上静态资源如CSS、JS以及图片均使用CDN分发。

3. 避免空src和href

当link标签的href属性或script标签的src属性为空时,浏览器在渲染页面时会将当前页面的URL作为其属性值,导致页面内容被加载。因此,应避免此类疏忽。

4. 为文件头设置过期时间

Expires用于设置文件的过期时间,通常适用于CSS、JS、图片资源。它可以使内容具有缓存性,从而在下一次访问相同资源时,直接从浏览器缓存读取,无需再次发出HTTP请求。

5. 使用gzip压缩内容

gzip可以压缩任何文本类型的响应,包括HTML、XML、JSON。这可以大幅缩小请求返回的数据量。

6. 将CSS置于顶部

网页资源加载顺序为从上至下,因此将CSS置于页面顶部可以优先渲染页面,使用户感觉页面加载速度更快。

7. 将JS置于底部

加载JS会对后续资源造成阻塞,必须等待JS加载完毕才能加载后续文件,因此将JS置于页面底部最后加载。

8. 避免使用CSS表达式

以下为CSS表达式的示例:

font-color: expression((new Date()).getHours()%3?"#FFFFFF":"#AAAAAA");

此表达式会持续在页面上计算样式,影响页面性能。此外,CSS表达式仅被IE支持。

9. 将CSS和JS置于外部文件中

目的是缓存文件,可参考原则4。但有时为了减少请求,也会直接将它们写入页面,需根据PV和IP的比例进行权衡。

10. 调整DNS查找次数

减少主机名可以节省响应时间。但同时,需要注意,减少主机名会减少页面中并行下载的数量。

IE浏览器在同一时刻只能从同一域名下载两个文件。当在一个页面显示多张图片时,IE用户的图片下载速度就会受到影响。因此,新浪会使用多个二级域名来存放图片。

以下为新浪微博的图片域名,我们可以看到它有多个域名,这样可以确保不同域名可以同时下载图片,而无需排队。但如果使用的域名过多,响应时间就会变慢,因为不同域名的响应时间不一致。

11. 精简CSS和JS

这涉及到CSS和JS的压缩。例如,以下新浪的一个CSS文件,将空格、回车等全部去除,减少文件大小。现在有很多压缩工具,主流的前端构建工具如grunt、glup等都可以进行CSS和JS文件的压缩。

12. 避免跳转

有一种现象可能看似无差别,但实际上进行了多次页面跳转。例如,当URL本应包含斜杠(/)时却被忽略。例如,当我们想要访问http:// baidu.com时,实际上返回的是一个包含301代码的跳转,它指向的是http:// baidu.com/(注意末尾的斜杠)。在nginx服务器中可以使用rewrite;在Apache服务器中可以使用Alias或mod_rewrite或the DirectorySlash来避免。

另一种是不使用域名之间的跳转,例如访问http:// baidu.com/bbs跳转到http:// bbs.baidu.com/。那么可以通过使用Alias或mod_rewirte建立CNAME(保存一个域名和另一个域名之间关系的DNS记录)来替代。

13. 删除重复的JS和CSS

重复调用脚本不仅会增加额外的HTTP请求,还会浪费计算资源。在IE和Firefox中,不管脚本是否可缓存,都存在重复运算JavaScript的问题。

14. 配置ETags

它用于判断浏览器缓存中的元素是否与服务器上的一致。比last-modified date更具有弹性,例如某个文件在1秒内修改了10次,Etag可以综合考虑Inode(文件的索引节点数)、MTime(修改时间)和Size进行精准判断,避开UNIX记录MTime只能精确到秒的问题。在服务器集群使用时,可取后两个参数。使用ETags减少Web应用带宽和负载。

15. 可缓存的AJAX

异步请求同样会造成用户等待,因此在使用ajax请求时,应主动告诉浏览器如果该请求有缓存,则直接使用缓存内容。如下代码片段,cache:true就是显式地要求如果当前请求有缓存,则直接使用缓存:

$.ajax({ url:'url', dataType:"json", cache: true, success: function(son, status){} });

16. 使用GET来完成AJAX请求

当使用XMLHttpRequest时,浏览器中的POST方法是一个“两步走”的过程:首先发送文件头,然后才发送数据。因此使用GET获取数据时更加有意义。

17. 减少DOM元素数量

这是一门大学问,这里可以引申出一堆优化的细节。想要具体研究的可以看后面推荐书籍。总之,大原则是减少DOM数量,这样可以减少浏览器的解析负担。

18. 避免404

例如,外链的CSS、JS文件出现问题返回404时,会破坏浏览器的并行加载。

19. 减少Cookie的大小

Cookie中不要塞太多东西,因为每个请求都必须携带它。

20. 使用无cookie的域

例如CSS、JS、图片等,客户端请求静态文件时,减少了Cookie的反复传输对主域名的影响。

21. 不要使用滤镜

IE独有的AlphaImageLoader属性用于修正7.0以下版本中显示PNG图片的半透明效果。这个滤镜的问题在于浏览器加载图片时会终止内容的呈现并冻结浏览器。在每一个元素(不仅仅是图片)上都会进行一次运算,增加了内存开支,因此它的问题是多方面的。

IE特有的属性AlphaImageLoader用于纠正7.0以下版本中展示PNG图像的半透明效果。该滤镜的弊端在于浏览器在加载图片时,会中断内容的显示并冻结浏览器。对于每一个元素(不仅限于图片),它都会进行一次计算,这增加了内存消耗,因此问题具有多面性。

完全避免使用AlphaImageLoader的最佳途径是使用PNG8格式进行替换,这种格式在IE中表现良好。如果确实需要使用AlphaImageLoader,请使用下划线_filter使其对IE7以上版本的用户失效。

  1. 不要在HTML中放大图片

    例如,你需要的图片尺寸是5050

    那就不用一张500500的大尺寸图片,以免影响加载速度。

  2. 缩小favicon.ico并缓存

    常见的前端性能提升手段及其收益有哪些

    前端是庞大的,包括 HTML、 CSS、 Javascript、Image、Flash等各种资源。前端优化是复杂的,针对各种资源都有不同的方法。那么,前端优化的目的是什么?

  3. 从用户角度而言,优化能让页面加载更快、对用户的操作响应更迅速,为用户提供更友好的体验。
  4. 从服务商角度而言,优化能减少页面请求数、或减小请求所占带宽,能节省可观的资源。

    总之,恰当的优化不仅能提升站点的用户体验,还能节省相当的资源利用。

前端优化的方法有很多,按范围大致可以分为两类,一类是页面级别的优化,例如 HTTP请求数、脚本的无阻塞加载、内联脚本的位置优化等;另一类是代码级别的优化,例如 Javascript中的DOM操作优化、CSS选择符优化、图片优化以及 HTML结构优化等等。另外,本着提高投入产出比的目的,下文提到的各种优化策略大致按照投入产出比从高到低的顺序排列。

一、页面级优化

1. 减少HTTP请求数

这条策略基本上所有前端人都知道,也是最重要最有效的。都说要减少HTTP请求,那么请求多了会怎么样呢?首先,每个请求都是有成本的,既包含时间成本也包含资源成本。一个完整的请求都需要经过 DNS寻址、与服务器建立连接、发送数据、等待服务器响应、接收数据这样一个“漫长”而复杂的过程。时间成本就是用户需要看到或者“感受”到这个资源是必须要等待这个过程结束的,资源上由于每个请求都需要携带数据,因此每个请求都需要占用带宽。另外,由于浏览器进行并发请求的请求数是有上限的(具体参见此处),因此请求数多了以后,浏览器需要分批进行请求,这会增加用户的等待时间,会给用户造成站点速度慢的印象,即使可能用户能看到的第一屏的资源都已经请求完了,但是浏览器的进度条会一直存在。

减少HTTP请求数的主要途径包括:

(1). 从设计实现层面简化页面

如果你的页面像百度首页一样简单,那么接下来的规则基本上都用不着了。保持页面简洁、减少资源的使用是最直接的。如果不是这样,你的页面需要华丽的皮肤,则继续阅读下面的内容。

(2). 合理设置HTTP缓存

缓存的力量是强大的,恰当的缓存设置可以大大减少HTTP请求。以有啊首页为例,当浏览器没有缓存的时候访问一共会发出78个请求,共600多K数据(如图1.1),而当第二次访问即浏览器已缓存之后访问则仅有10个请求,共20多K数据(如图1.2)。(这里需要说明的是,如果直接F5刷新页面的话效果是不一样的,这种情况下请求数还是一样,不过被缓存资源的请求服务器是304响应,只有Header没有Body,可以节省带宽)

怎样才算合理设置?原则很简单,能缓存越多越好,能缓存越久越好。例如,很少变化的图片资源可以直接通过HTTP Header中的Expires设置一个很长的过期头;变化不频繁而又可能会变的资源可以使用Last-Modified来做请求验证。尽可能让资源能够在缓存中待得更久。关于HTTP缓存的具体设置和原理此处就不再详述了,有兴趣的可以参考下列文章:

HTTP1.1协议中关于缓存策略的描述

Fiddler HTTP Performance中关于缓存的介绍

(3). 资源合并与压缩

如果可以的话,尽可能将外部的脚本、样式进行合并,多个合为一个。另外, CSS、 Javascript、Image都可以用相应的工具进行压缩,压缩后往往能省下不少空间。

(4). CSS Sprites

合并CSS图片,减少请求数的又一个好办法。

(5). Inline Images

使用data: URL scheme的方式将图片嵌入到页面或CSS中,如果不考虑资源管理上的问题的话,不失为一个好办法。如果是嵌入页面的话换来的是增大了页面的体积,而且无法利用浏览器缓存。使用在CSS中的图片则更为理想一些。

(6). Lazy Load Images(自己对这一块的内容还是不了解)

这条策略实际上并不一定能减少HTTP请求数,但是却能在某些条件下或者页面刚加载时减少HTTP请求数。对于图片而言,在页面刚加载的时候可以只加载第一屏,当用户继续往后滚屏的时候才加载后续的图片。这样一来,假如用户只对第一屏的内容感兴趣时,那剩余的图片请求就都节省了。有啊首页曾经的做法是在加载的时候把第一屏之后的图片地址缓存在Textarea标签中,待用户往下滚屏的时候才“惰性”加载。

  1. 将外部脚本置底(将脚本内容在页面信息内容加载后再加载)

    前文有谈到,浏览器是可以并发请求的,这一特点使得其能够更快的加载资源,然而外链脚本在加载时却会阻塞其他资源,例如在脚本加载完成之前,它后面的图片、样式以及其他脚本都处于阻塞状态,直到脚本加载完成后才会开始加载。如果将脚本放在比较靠前的位置,则会影响整个页面的加载速度从而影响用户体验。解决这一问题的方法有很多,在这里有比较详细的介绍(这里是译文和更详细的例子),而最简单可依赖的方法就是将脚本尽可能的往后挪,减少对并发下载的影响。

前文已有论述,浏览器支持并发发起请求,这一特性使得它能够更快地加载资源。但外链脚本在加载过程中会阻碍其他资源的加载,比如在脚本加载完成前,其后的图片、样式及其他脚本都处于停滞状态,直至脚本加载完毕才重新启动加载。若将脚本置于页面前端,则会拖慢整个页面的加载速度,进而影响用户体验。解决这一问题的途径众多,此处将进行较为详尽的介绍(此处为译文及更详细的案例),而最简便可行的方法是将脚本尽量后移,以降低对并发下载的干扰。

  1. 异步执行内联脚本(其原理与上文所述相同,确保脚本在页面内容之后加载。)

    内联脚本对性能的影响相较于外部脚本,有过之而无不及。首先,与外部脚本类似,内联脚本在执行过程中同样会阻塞并发请求。其次,由于浏览器在页面处理方面是单线程的,当内联脚本在页面渲染前执行时,页面的渲染工作将被推迟。简言之,内联脚本在执行时,页面将处于空白状态。鉴于以上两点原因,建议将执行时间较长的内联脚本异步执行,异步方法众多,如使用script元素的defer属性(存在兼容性问题及其他问题,如不能使用document.write)、使用setTimeout,此外,HTML5中引入的Web Workers机制,恰好可以解决此类问题。

  2. 懒加载JavaScript(仅在需要加载时加载,一般情况下不加载信息内容。)

    随着JavaScript框架的普及,越来越多的网站开始采用框架。然而,一个框架通常包含众多功能实现,而这些功能并非每个页面都需使用。若下载了不必要的脚本,则等同于资源浪费——既浪费了带宽,又浪费了执行时间。目前,主要有两种做法:一种是为流量较大的页面定制专用的mini版框架,另一种则是懒加载。YUI便采用了第二种方式,在其实现中,最初仅加载核心模块,其他模块可待需要使用时再加载。

  3. 将CSS置于HEAD中

    若将CSS置于BODY中或其他位置,则浏览器可能尚未下载和解析CSS,就已开始渲染页面,导致页面从无CSS状态跳转到CSS状态,用户体验较差。此外,部分浏览器会在CSS下载完成后才开始渲染页面,若CSS置于页面下方,则会导致浏览器推迟渲染时间。

  4. 异步请求回调(将一些行为样式提取出来,逐步加载信息内容)

    某些页面可能存在使用script标签异步请求数据的需求。例如:

    JavaScript:

    function myCallback(info){

    //在此处进行操作

    }

    HTML:

    cb返回的内容:

    myCallback('Hello world!');

    像上述这种方式直接在页面上编写标签,对页面性能也会产生一定影响,增加了页面首次加载的负担,推迟了DOMLoaded和window.onload事件的触发时机。如果时效性允许,可以考虑在DOMLoaded事件触发时加载,或使用setTimeout等方式灵活控制加载时机。

  5. 减少不必要的HTTP跳转

    对于以目录形式访问的HTTP链接,许多人会忽略链接末尾是否带'/'。若你的服务器对此有区分,则也需要注意,这可能导致隐藏的301跳转,增加多余请求。具体可参考以下图表,其中第一个链接是以无'/'结尾的方式访问的,于是服务器进行了一次跳转。

  6. 避免重复的资源请求

    这种情况主要是由于疏忽或页面由多个模块拼接而成,然后每个模块都请求了相同的资源,导致资源重复请求。

二、代码级优化

1. JavaScript

(1). DOM

DOM操作是脚本中最耗费性能的一类操作,例如增加、修改、删除DOM元素或对DOM集合进行操作。若脚本中包含大量DOM操作,则需注意以下几点:

a. HTML Collection(HTML收集器,返回的是一个数组内容信息)

在脚本中,document.images、document.forms、getElementsByTagName()返回的都是HTMLCollection类型的集合。在平时使用时,大多将其作为数组来使用,因为它有length属性,也可以使用索引访问每个元素。但在访问性能上,它比数组要差很多,原因在于这个集合并不是一个静态的结果,它表示的仅仅是一个特定的查询,每次访问该集合时都会重新执行查询,从而更新查询结果。所谓的“访问集合”包括读取集合的length属性、访问集合中的元素。

因此,当你需要遍历HTML Collection时,尽量将其转换为数组后再访问,以提高性能。即使不转换为数组,也请尽可能少地访问它,例如在遍历时可以将length属性、成员保存到局部变量后再使用局部变量。

b. Reflow& Repaint

除了上述一点外,DOM操作还需考虑浏览器的Reflow和Repaint,因为这些都是需要消耗资源的。具体可参考以下文章:

如何减少浏览器的repaint和reflow?

Understanding Internet Explorer Rendering Behaviour

Notes on HTML Reflow

(2). 谨慎使用with

with(obj){ p= 1};代码块的行为实际上是修改了代码块中的执行环境,将obj放在了其作用域链的最前端。在with代码块中访问非局部变量时,都会先从obj上开始查找,如果没有再依次按作用域链向上查找。因此,使用with相当于增加了作用域链长度。而每次查找作用域链都会消耗时间,过长的作用域链会导致查找性能下降。

因此,除非你能肯定在with代码中只访问obj中的属性,否则谨慎使用with,可使用局部变量缓存需要访问的属性。

因此,除非你能确定在 with代码中仅访问 obj的属性,否则请谨慎使用with,可考虑使用局部变量来缓存需要访问的属性。

(3). 避免运用 eval及 Function

每次使用eval或Function构造函数处理字符串形式的源代码时,脚本引擎都需要将源代码转换成可执行代码。这种操作非常耗费资源——通常比简单的函数调用慢100倍以上。

eval函数的效率尤为低下,因为事先无法预知传递给eval的字符串内容,eval在其上下文中解析要处理的代码,也就是说编译器无法优化上下文,因此只能由浏览器在运行时解析代码。这对性能影响极大。

Function构造函数相较于eval略优,因为使用此代码不会影响周围代码;但其速度仍然较慢。

此外,使用eval和Function也不利于JavaScript压缩工具进行压缩。

(4). 减少作用域链查询(此部分涉及到一些相关内容的问题)

前文已提及作用域链查询问题,这一点在循环中尤其需要注意。如果在循环中需要访问非本作用域下的变量时,请在遍历之前用局部变量缓存该变量,并在遍历结束后再重写那个变量,这一点对全局变量尤其重要,因为全局变量处于作用域链的最顶端,访问时的查找次数是最多的。

低效的写法:

//全局变量

var globalVar= 1;

function myCallback(info){

for( var i= 100000; i--;){

//每次访问 globalVar都需要查找到作用域链最顶端,本例中需要访问 100000次

globalVar+= i;

}

}

高效的写法:

//全局变量

var globalVar= 1;

function myCallback(info){

//局部变量缓存全局变量

var localVar= globalVar;

for( var i= 100000; i--;){

//访问局部变量是最快的

localVar+= i;

}

//本例中只需要访问 2次全局变量

在函数中只需要将 globalVar中内容的值赋给localVar中区

globalVar= localVar;

}

此外,要减少作用域链查询还应该减少闭包的使用。

(5). 数据访问

JavaScript中的数据访问包括直接量(字符串、正则表达式)、变量、对象属性以及数组,其中对直接量和局部变量的访问是最快的,对对象属性以及数组的访问需要更大的开销。当出现以下情况时,建议将数据放入局部变量:

a.对任何对象属性的访问超过1次

b.对任何数组成员的访问次数超过1次

另外,还应当尽可能减少对对象以及数组深度查找。

(6). 字符串连接

在JavaScript中使用"+"号来连接字符串效率较低,因为每次运行都会开辟新的内存并生成新的字符串变量,然后将拼接结果赋值给新变量。与之相比,更为高效的做法是使用数组的join方法,即将需要连接的字符串放在数组中,最后调用其join方法得到结果。不过由于使用数组也有一定的开销,因此当需要连接的字符串较多时可以考虑使用此方法。

关于JavaScript优化的更详细介绍请参考:

Write Efficient Javascript(PPT)

Efficient JavaScript

  1. CSS选择器

    在大多数人的观念中,都觉得浏览器对CSS选择器的解析是从左往右进行的,例如

toc A{ color:#444;}

这样一个选择器,如果是从右往左解析则效率会很高,因为第一个ID选择器基本上就把查找的范围限定了,但实际上浏览器对选择器的解析是从右往左进行的。如上面的选择器,浏览器必须遍历查找每一个A标签的祖先节点,效率并不像之前想象的那样高。根据浏览器的这一行为特点,在编写选择器时需要注意很多事项,有人已经一一列举了,详情参考此处。

  1. HTML

    对HTML本身的优化现如今也越来越多地受到关注,详情可以参见这篇总结性文章。

  2. 图片压缩

    图片压缩是个技术活,不过现如今这方面的工具也非常多,压缩之后往往能带来不错的效果,具体的压缩原理以及方法在《Even Faster Web Sites》第10章有很详细的介绍,有兴趣的可以去看看。

总结

本文从页面级以及代码级两个层面总结了前端优化的各种方法,这些方法基本上都是前端开发人员在开发过程中可以借鉴和实践的。除此之外,完整的前端优化还应该包括很多其他的途径,例如CDN、Gzip、多域名、无Cookie服务器等等,由于对于开发人员的可操作性并不强,在此也就不多叙述了,详细的可以参考Yahoo和Google的这些“金科玉律”。

以上所转载内容均来自于网络,不为其真实性负责,只为传播网络信息为目的,非商业用途,如有异议请及时联系btr2020@163.com,本人将予以删除。

猜你喜欢
文章评论已关闭!
picture loss