本页二维码,扫一扫分享到朋友圈
朋友圈

如何提高网站性能


阅读179 评论0 赞 57返回首页    go 编程与技术  go html


性能黄金法则(Performance Golden Rule)解释了只有10%~20%的最终用户响应时间花在接受所请求的用户HTML文档上,剩余的80%~90%时间花在为HTML文档所引用的所有组件(图片、脚本、样式表等)进行的HTTP请求上,最终用户响应时间花费在页面组件上

    ——Steve Sounders


  1 、文件合并(减少HTTP请求数量)


  CSS Sprites


    利用css sprites将网站用到的图片合并成一张图片,通过background-position、width、height控制背景图位置来使用某一个图标,这种方式可以将多个图片请求缩减为一次,生成css sprites的工具也很多,grunt和gulp中都有相关的插件,CssGaga也不错。


  合并js和css


    和精灵图一样,合并css和js文件也是减少HTTP请求很重要的方式,对css文件的合并目前来说没有什么争议,但是对于当前js模块化盛行,将所有js文件合并成一个文件,仿佛是一种倒退。正确的方式是遵守编译型语言的模式,保持js的模块化,在生成过程中只对初始请求用到的js文件生成目标文件。


  2 、使用内容发布网络(减少HTTP请求时间)


    HTTP请求时间另一个影响因素是你与网站web服务器所处的距离,显然距离越远,请求所需的时间也越久,通过CDN可以大大改善这一点。


    CDN是分布在多个不同地理位置的web服务器,用于更加有效的向用户发布内容。CDN最主要的功能是给终端用户存放静态文件,另外也提供下载、安全服务等功能。


  3 、设置浏览器缓存(避免重复HTTP请求)


  使用Expire/Cache-control


    浏览器通过使用缓存可以避免每次都进行重复的请求,HTTP 1.0和HTTP1.1分别有不同的缓存实现方式,Expires(1.0)和Cache-control(1.1)。Web服务器通过expires告诉客户端在指定的时间内,都使用该文件的缓存副本,不再向服务端重复发出请求,例如:

1

Expires: Thu, 01 Dec 2016 16:00:00 GMT (GMT格式)

    这个设置意味着截止到2016年12月1日,都可以使用该缓存副本,无需再发出请求。


    Expires这种通过截止日期的方式,存在一个限制:要求客户端和服务端时钟严格同步,而HTTP 1.1引入的Cache-Control通过指定一个以秒为单位的时间指定缓存日期,则不存该限制,例如:

1

Cache-Control: max-age=31536000

    这个设置意味缓存时间为一年,推荐使用Cache-Control,但是在支持HTTP 1.1的情况下,另外要注意的一点:Cache-Control和Expire同时存在时,Cache-Control具有更高的优先级。


  配置或移除ETag


    使用Expire/Cache-Control可以避免第二次访问时,使用本地缓存避免重复HTTP请求,提高网站速度。然而,在用户点击了浏览器刷新或者在expire已过期的情况下,仍然会向服务端发出HTTP GET请求,而此时如果该文件并没有发生变化,服务端不会返回整个文件而是会返回304 Not Modified状态码。

    服务端判断该文件是否发生变化的依据有两个:Last-Modified(最新修改日期)和ETag(实体标签);

    ETag(Entity Tags)是在HTTP 1.1引入的,与Last-Modified同时存在时要有更高的优先级。服务端通过对比客户端发来的ETag(If-None-Match)和当前ETag,若相同返回304 Not Modifed,否则返回整个文件以及200 OK。

    ETag存在一个问题:当浏览器向一个服务器发送GET请求原始组件,之后又向另一台服务器请求该组件时,ETag是不匹配的,当然,如果你的网站寄宿在一台服务器上不存在这个问题,而现在很多网站使用多台服务器,ETag的存在就大大降低验证有效性的成功率。

    存在这个问题是时的解决办法是对ETag进行配置,移除服务器innode值只保留修改时间戳和大小作为ETag值,或者直接移除ETag,使用Last-Modified来验证文件有效性。


  4 、压缩组件(减小HTTP请求大小)


    通过对HTTP传输的文件进行压缩减小HTTP请求的大小,提高请求速度,GZIP是目前最常用也是最有效的压缩方式。


    然而,并非所有的资源文件都需要压缩,压缩的成本包括服务端需要花费CPU周期进行压缩,而客户端也需要对压缩文件进行解压缩,必须结合自己网站进行权衡。现在绝大多数网站都对其HTML文档进行压缩,部分网站选择对js、css进行压缩,几乎没有网站对图片、PDF等文件进行GZIP压缩,原因在于这些文件是已经被压缩过的,采用HTTP压缩已经被过压缩的东西并不能使它更小。事实上,添加标头,压缩字典,并校验响应体实际上使它变得更大,而且还浪费了CPU。


    如何对网站开启GZIP,需要在所使用的web服务器(IIS、Nginx、Apache等)中进行设置。


  5、 CSS文件放在首部


    将CSS文件放在首部和放在尾部,并不影响HTTP请求,因此从请求时间上来讲是一致的,然而从用户体验的角度,将CSS文件放在首部,会获得更好的用户体验。


    原因在于浏览器是从上到下依次解析html文档,将CSS文件置于头部,页面会首先对CSS文件发出请求,随后加载DOM树并对其进行渲染,页面会逐步呈现在用户面前。


    而与之相反,如果将CSS文件放置在尾部,页面加载完整DOM之后请求CSS文件,然后对整个DOM树渲染并呈现给用户,从用户的角度,在css文件没有请求完成之前,整个页面是出于白屏状态的,白屏是浏览器的一种行为,David Hyatt对其的解释是这样的

  在样式树没有完全加载之前,渲染dom树就是一种浪费,因为在样式树加载完成之后会再次渲染,出现FOUC(无样式内容闪烁)问题。


    另外要注意的一点,使用link而不是@import引入css样式表,使用@import引入的样式即使写在首部,也会在文档最后加载。


  6 、JS文件放在尾部


    HTTP请求是并行的,不同浏览器并行下载的数目也不一样(2、4、或者8个),并行下载提高了HTTP请求的速度。而将JS文件放在首部,不仅会阻塞后面文件的下载而且会阻塞页面的渲染。


    为什么会这样呢?原因有两个:

  •   JS文件中可能存在document.write修改页面的内容,因此页面会在脚本执行完成之后才可使渲染。

  •   不同JS文件不管大小如何,可能存在依赖关系,因此必须按照顺序进行执行,因此在加载脚本的时候并行下载是禁止的。

    所以,最好的方式是讲js文件放置在尾部,等页面所有可视化组件加载完成之后再进行请求,提高用户体验。



      7、利用js作用域链


  作用域链(scope chain)


  当执行一段JavaScript代码(全局代码或函数)时,JavaScript引擎会创建为其创建一个作用域又称为执行上下文(Execution Context),在页面加载后会首先创建一个全局的作用域,然后每执行一个函数,会建立一个对应的作用域,从而形成了一条作用域链。每个作用域都有一条对应的作用域链,链头是全局作用域,链尾是当前函数作用域。


  作用域链的作用是用于解析标识符,当函数被创建时(不是执行),会将this、arguments、命名参数和该函数中的所有局部变量添加到该当前作用域中,当JavaScript需要查找变量X的时候(这个过程称为变量解析),它首先会从作用域链中的链尾也就是当前作用域进行查找是否有X属性,如果没有找到就顺着作用域链继续查找,直到查找到链头,也就是全局作用域链,仍未找到该变量的话,就认为这段代码的作用域链上不存在x变量,并抛出一个引用错误(ReferenceError)的异常。


  管理好作用域链的深度,是一种只要少量工作就能提高性能的简易方法,我们应避免因无意中增长了作用域链而导致执行速度变得缓慢。


  使用局部变量(尽量缩短作用域链)


  如果理解了作用域链的概念,那么我们应该清楚JavaScript引擎对变量的解析时间跟作用域链的深度有关,显而易见,局部变量由于处于链尾,存取速度是最快的,因此,一个好的经验是:任何非局部变量使用超过一次时,请使用局部变量将其存储下来,例如:

1

function changeDiv(){

2

   document.getELementById('myDiv').className = 'changed';

3

   document.getELementById('myDiv').style.height = 150;

4

}

  这里myDiv这个dom元素被引用了两次,为了更快的引用,我们应该用一个局部变量将其存储下来,这样做的好处不仅缩短了作用域链,而且避免了DOM元素的重复查询:

1

function changeDiv(){

2

   var myDivStyle = document.getElementById('myDiv').style;

3

   myDiv.className = 300;

4

   myDiv.style.height = 150;

5

}


  避免使用with(不要增长作用域链)


  一般在代码执行过程中,函数的作用域链是固定的,然而with可以临时增长函数的作用域链。with用于将对象属性作为局部变量来显示,使其便于访问,例如:

01

var user = {

02

    name:'vicfeel',

03

    age:'23'

04

};

05

 

06

function showUser(){

07

    var local = 0;

08

    with(user){

09

        console.log("姓名" + name);

10

        console.log("年龄" + age);

11

        console.log(local);

12

    }

13

}

14

 

15

showUser();

  这个例子中,通过with在showUser作用域链的链尾中又增加了一个临时作用域,该作用域存储着user对象的所有属性,也就是增长了with这段代码的作用域链,在这段代码中,局部变量像local从链尾的第一个对象变成了第二个,自然减慢了标识符的存取。直到with语句结束,作用域链恢复增长。正因为with的这个缺陷,我们应尽量避免使用with关键字。


  8、更合理的流控制


  JavaScript与其它编程语言一样,拥有一些流控制语句(循环、条件等),在每个环节上使用恰当的语句能极大的提高脚本的运行速度。


  快速条件判断


  提到条件判断,首先要避免的一种使用方式:

01

if(value == 0){

02

    return result0;

03

}

04

else if(value == 1){

05

    return result1;

06

}

07

else if(value == 2){

08

    return result2;

09

}

10

else if(value == 3){

11

    return result3;

12

}

13

else if(value == 4){

14

    return result4;

15

}

16

else if(value == 5){

17

    return result5;

18

}

19

else if(value == 6){

20

    return result6;

21

}

22

else{

23

    return result7;

24

}

  这种使用if进行条件判断的方式存在的主要问题是层次太深,当我要value = 7时,消耗时间要比value = 0长很多,大大损耗了性能,同时可读性很差。

  一种更好的方式,利用switch进行判断。

01

switch(value){

02

    case 0:

03

        return result0;

04

    case 1:

05

        return result1;

06

    case 2:

07

        return result2;

08

    case 3:

09

        return result3;

10

    case 4:

11

        return result4;

12

    case 5:

13

        return result5;

14

    case 6:

15

        return result6;

16

    default:

17

        return result7;

18

}

  这样不仅提高了可读性,查询时间也要比if更快。但是如果只有一两个条件时,if是比switch更快的

  在JavaScript中,条件查询还有另外一种方式,之前的例子是根据值返回不同的值,刚好可以利用数组实现hash表的映射查询。

1

//定义数组

2

var results = [result0,result1,result2,result3,result4,result5,result6,result7];

3

//查询结果

4

return results[value];

  这种数组的方式,在查询范围很大时才更加有效,因为它不必检测上下边界,只需要填入索引值就可以查询了。它的局限性在于条件对应的是单一值,而不是一系列操作。因此要综合实际情况,选择合适的条件判断方式,发挥性能最大化。


  快速循环


  JavaScript中存在4种循环方式for循环、do-while循环、while循环和for-in循环。下面是一个很常用的循环使用方式:

1

var values = [1,2,3,4,5];

2

for(var i = 0;i < values.length;i++){

3

    process(values[i]);

4

}

  我们可以看到,这段代码最明显可以优化的地方在于values.length,每次循环i都要和values的长度进行比较,而查询属性要比局部变量更耗时,如果循环次数越大,这种耗时就越明显,因此可以这样优化:

1

var values = [1,2,3,4,5];

2

var length = values.length;//局部变量存储数组长度

3

for(var i = 0;i < length;i++){

4

    process(values[i]);

5

}

  这段代码还可以继续优化,将循环变量递减到0,而不是递加到总长度。

1

var values = [1,2,3,4,5];

2

var length = values.length;

3

for(var i = length;i--;){   //递减到0

4

    process(values[i]);

5

}

  这里将循环结束改造为与0比较,所以每个循环的速度更快了,根据循环的复杂度不同,这种简单改变可以比原来节约大概50%的时间。


来源作者:vicfeel



  上一篇:go 如何将 HTML5 性能发挥到极致
  下一篇:go HTML 5.1将于9月份正式发布 更新内容预览


评论


用QQ登录管理/创建网站 用微博登录管理/创建网站   发布于:08-26