网站前端优化几要素

前言: 

在同样的网络环境下,两个同样能满足你的需求的网站,一个“Duang”的一下就加载出来了,一个纠结了半天才出来,你会选择哪个?研究表明:用户最满意的打开网页时间是2-5秒,如果等待超过10秒,99%的用户会关闭这个网页。也许这样讲,各位还不会有太多感触,接下来我列举一组数据:Google网站访问速度每慢400ms就导致用户搜索请 求下降0.59%;Amazon每增加100ms网站延迟将导致收入下降1%;雅虎如果有400ms延迟会导致流量下降5-9%。网站的加载速度严重影响了用户体验,也决定了这个网站的生死存亡。

可能有人会说:网站的性能是后端工程师的事情,与前端并无多大关系。我只能说,too young too simple。事实上,只有10%~20%的最终用户响应时间是用在从Web服务器获取HTML文档并传送到浏览器的,那剩余的时间去哪儿了?来瞄一下性能黄金法则:

只有10%~20%的最终用户响应时间花在了下载HTML文档上。其余的80%~90%时间花在了下载页面中的所有组件上。

接下来我们将研究一下前端攻城狮如何来提高页面的加载速度。

一、尽量减少 HTTP 请求

有几种常见的方法能切实减少 HTTP 请求:

1、 合并脚本跟样式文件,如可以把多个 CSS 文件合成一个,把多个 JS 文件合成一个。

2、 CSS Sprites 利用 CSS background 相关元素进行背景图绝对定位,把多个图片合成一个图片。

3、字体图标,如iconfont.cn,font awesome

二、使用CDN

CDN(内容发布网络)是一组分布在多个不同地理位置的Web服务器,用于更加有效地向用户发布内容。在优化性能时,向特定用户发布内容的服务器的选择基于对网络慕课拥堵的测量。例如,CDN可能选择网络阶跃数最小的服务器,或者具有最短响应时间的服务器。

CDN还可以进行数据备份、扩展存储能力,进行缓存,同时有助于缓和Web流量峰值压力。

CDN的缺点:

1、响应时间可能会受到其他网站流量的影响。CDN服务提供商在其所有客户之间共享Web服务器组。

2、如果CDN服务质量下降了,那么你的工作质量也将下降

3、无法直接控制组件服务器

三、添加Expires头

页面的初次访问者会进行很多HTTP请求,但是通过使用一个长久的Expires头,可以使这些组件被缓存,下次访问的时候,就可以减少不必要的HTPP请求,从而提高加载速度。

Web服务器通过Expires头告诉客户端可以使用一个组件的当前副本,直到指定的时间为止。例如:

Expires: Fri, 18 Mar 2016 07:41:53 GMT

Expires缺点: 它要求服务器和客户端时钟严格同步;过期日期需要经常检查

HTTP1.1中引入Cache-Control来克服Expires头的限制,使用max-age指定组件被缓存多久。

Cache-Control: max-age=12345600

若同时制定Cache-Control和Expires,则max-age将覆盖Expires头

四、压缩组件

从HTTP1.1开始,Web客户端可以通过HTTP请求中的Accept-Encoding头来表示对压缩的支持

Accept-Encoding: gzip,deflate

如果Web服务器看到请求中有这个头,就会使用客户端列出来的方法中的一种来进行压缩。Web服务器通过响应中的Content-Encoding来通知 Web客户端。

Content-Encoding: gzip

五、将样式表放在头部

将样式表放在文档底部会阻止浏览器中的内容逐步出现。为了避免当样式变化时重绘页面元素,浏览器会阻塞内容逐步呈现,造成“白屏”。这源自浏览器的行为:如果样式表仍在加载,构建呈现树就是一种浪费,因为所有样式表加载解析完毕之前务虚会之任何东西

六、将脚本放在底部

更样式表相同,脚本放在底部对于实际页面加载的时间并不能造成太大影响,但是这会减少页面首屏出现的时间,使页面内容逐步呈现。

js的下载和执行会阻塞Dom树的构建(严谨地说是中断了Dom树的更新),所以script标签放在首屏范围内的HTML代码段里会截断首屏的内容。

下载脚本时并行下载是被禁用的——即使使用了不同的主机名,也不会启用其他的下载。因为脚本可能修改页面内容,因此浏览器会等待;另外,也是为了保证脚本能够按照正确的顺序执行,因为后面的脚本可能与前面的脚本存在依赖关系,不按照顺序执行可能会产生错误。

七、避免CSS表达式

CSS表达式是动态设置CSS属性的一种强大并且危险的方式,它受到了IE5以及之后版本、IE8之前版本的支持。

八、使用外部的JavaScript和CSS

内联脚本或者样式可以减少HTTP请求,按理来说可以提高页面加载的速度。然而在实际情况中,当脚本或者样式是从外部引入的文件,浏览器就有可能缓存它们,从而在以后加载的时候能够直接使用缓存,而HTML文档的大小减小,从而提高加载速度。

九、减少DNS查找

当我们在浏览器的地址栏输入网址(譬如: www.linux178.com) ,然后回车,回车这一瞬间到看到页面到底发生了什么呢?

域名解析 --> 发起TCP的3次握手 --> 建立TCP连接后发起http请求 --> 服务器响应http请求,浏览器得到html代码 --> 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等) --> 浏览器对页面进行渲染呈现给用户

十、精简JavaScript/css代码

精简就是从代码中移除不必要的字符以减少文件大小,降低加载的时间。代码精简的时候会移除不必要的空白字符(空格,换行、制表符),这样整个文件的大小就变小了。

十一、避免重定向

重定向用于将用户从一个URL重新路由到另一个URL。

常用重定向的类型

301:永久重定向,主要用于当网站的域名发生变更之后,告诉搜索引擎域名已经变更了,应该把旧域名的的数据和链接数转移到新域名下,从而不会让网站的排名因域名变更而受到影响。

302:临时重定向,主要实现post请求后告知浏览器转移到新的URL。

304:Not Modified,主要用于当浏览器在其缓存中保留了组件的一个副本,同时组件已经过期了,这是浏览器就会生成一个条件GET请求,如果服务器的组件并没有修改过,则会返回304状态码,同时不携带主体,告知浏览器可以重用这个副本,减少响应大小。

十二、删除重复脚本

在团队开发一个项目时,由于不同开发者之间都可能会向页面中添加页面或组件,因此可能相同的脚本会被添加多次。

重复的脚本会造成不必要的HTTP请求(如果没有缓存该脚本的话),并且执行多余的JavaScript浪费时间,还有可能造成错误。

如何避免重复脚本呢?

1. 形成良好的脚本组织。重复脚本有可能出现在不同的脚本包含同一段脚本的情况,有些是必要的,但有些却不是必要的,所以需要对脚本进行一个良好的组织。

2. 实现脚本管理器模块。

十三、配置ETag

实体标签(EntityTag)是唯一标识了一个组件的一个特定版本的字符串,是web服务器用于确认缓存组件的有效性的一种机制,通常可以使用组件的某些属性来构造它。

十四、使Ajax可缓存

POST的请求,是不可以在客户端缓存的,每次请求都需要发送给服务器进行处理,每次都会返回状态码200。(可以在服务器端对数据进行缓存,以便提高处理速度)

GET的请求,是可以(而且默认)在客户端进行缓存的,除非指定了不同的地址,否则同一个地址的AJAX请求,不会重复在服务器执行,而是返回304。

十五、减少 DOM 元素数量

访问DOM元素是有代价的,修改DOM元素则更为昂贵,因为它会导致浏览器重新计算页面的几何变化。 

最坏的情况是在循环中访问修改元素,尤其是对HTML元素集合循环操作。

十六、最小化 iframe 的数量

在写网页的时候,我们可能会用到iframe,iframe的好处是它完全独立于父文档。iframe中包含的JavaScript文件访问其父文档是受限的。例如,来自不同域的iframe不能访问其父文档的Cookie。

即使iframe是空的,其开销也会很高,而且他会阻塞onload事件。所以,我们应该尽可能避免iframe的使用。

十七、杜绝 http 404 错误

十八、CSS选择器优化

1、在谈论选择器优化之前,我们先简单介绍一下选择器的类型:

ID选择器 : #id;

类选择器: .class

标签选择器: a

兄弟选择器:#id + a 

子选择器: #id > a

后代选择器: #id a

通赔选择器: *

属性选择器: input[type='input']

伪类和伪元素:a:hover , div:after

组合选择器:#id,.class

2、浏览器的匹配规则

#abc > a怎么匹配?  有人可能会以为:先找到id为abc的元素,再查找子元素为a的元素!!too young,too simple!

其实,浏览器时从右向左匹配选择符的!!!那么上面的写法效率就低了:先查找页面中的所有a标签,在看它的父元素是不是id为abc

知道了浏览器的匹配规则我们就能尽可能的避免开销很大的选择器了:

避免通配规则

除了 * 之外,还包括子选择器、后台选择器等。

而它们之间的组合更加逆天,譬如:li *

浏览器会查找页面的所有元素,然后一层一层地寻找他的祖先,看是不是li,这对可能极大地损耗性能。

不限定ID选择器

ID就是唯一的,不要写成类似div#nav这样,没必要。 

不限定class选择器

我们可以进一步细化类名,譬如li.nav  写成 nav-item

尽量避免后代选择器

通常后代选择器是开销最高的,如果可以,请使用子选择器代替。

替换子选择器

如果可以,用类选择器代替子选择器,譬如

nav > li 改成 .nav-item

依靠继承

了解那些属性可以依靠继承得来,从而避免重复设定规则。

3、关键选择符

选择器中最右边的选择符成为关键选择符,它对浏览器执行的工作量起主要影响。

举个栗子:

div div li span.class-special

乍一看,各种后代选择器组合,性能肯定不能忍。其实仔细一想,浏览器从右向左匹配,如果页面中span.class-special的元素只有一个的话,那影响并不大啊。

反过来看,如果是这样

span.class-special li div div ,尽管span.class-special很少,但是浏览器从右边匹配,查找页面中所有div在层层向上查找,那性能自然就低了。

4、重绘与回流

优化css选择器不仅仅提高页面加载时候的效率,在页面回流、重绘的时候也可以得到不错的效果,那么接下来我们说一下重绘与回流。

4.1、从浏览器的渲染过程谈起

解析HTML构建dom树→构建render树→布局render树→绘制render树

1)构建dom树

根据获得的html代码生成一个DOM树,每个节点代表一个HTML标签,根节点是document对象。dom树种包含了所有的HTML标签,包括未显示的标签(display:none)和js添加的标签。

2)构建cssom树

将得到所有样式(浏览器和用户定义的css)除去不能识别的(错误的以及css hack),构建成一个cssom树

3)cssom和dom结合生成渲染树,渲染树中不包括隐藏的节点包括(display:none、head标签),而且每个节点都有自己的style属性,渲染树种每一个节点成为一个盒子(box)。注意:透明度为100%的元素以及visibility:hidden的元素也包含在渲染树之中,因为他们会影响布局。

4)浏览器根据渲染树来绘制页面

4.2、重绘(repaint)与回流(reflow)

1)重绘   当渲染树中的一部分或者全部因为页面中某些元素的布局、显示与隐藏、尺寸等改变需要重新构建,这就是回流。每个页面至少会发生一次回流,在页面第一次加载的时候发生。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。

2. 当渲染树中的一些元素需要更新属性,而这些属性不会影响布局,只影响元素的外观、风格,比如color、background-color,则称为重绘。

注意:回流必将引起重绘,而重绘不一定会引起回流。

4.3、回流何时发生:

当页面布局和几何属性改变时就需要回流。下述情况会发生浏览器回流:

1、添加或者删除可见的DOM元素;

2、元素位置改变;

3、元素尺寸改变——边距、填充、边框、宽度和高度

4、内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;

5、页面渲染初始化;

6、浏览器窗口尺寸改变——resize事件发生时;

4.4、如何影响性能

页面上任何一个结点触发reflow,都会导致它的子结点及祖先结点重新渲染。

每次重绘和回流发生时,浏览器会根据对应的css重新绘制需要渲染的部分,如果你的选择器不优化,就会导致效率降低,所以优化选择器的重要性可见一斑。

posted @ 2017-07-21 14:03  豌豆尖  阅读(200)  评论(0编辑  收藏  举报