前端性能优化-基础认知

前端性能优化-基础认知

内容目录

一、减少HTTP请求 1
1.1使用图片地图: 1
1.2 CSS Sprites : 2
1.3 字体图标 : 2
1.4 合并脚本和样式 : 2
二、使用CDN 2
三、合理使用缓存 3
1.1添加Expires头 3
1.2 使Ajax可缓存 3
1.3 注意控制Cookie大小和污染 3
四、压缩组件 4
1.1合理切图 4
1.2 压缩减小css及js文件体积 4
五、对js的优化 4
1.1放置位置 (页面底部) 4
1.2 精简JavaScript代码 5
1.3 JavaScript代码优化 7
1.3.1 浅谈JavaScript作用域 7
1.3.2 使用局部变量 8
1.3.3 避免增长作用域链 9
1.3.4 优化字符串连接 9
1.3.5 条件判断优化 9
1.3.6 优化循环 10
1.3.7 事件委托优化 11
六、对css的优化 13
1.1放置位置 (页面头部) 13
1.2 css的精简 13
1.3 css选择器优化 14
1.3.1 了解浏览器的匹配规则 14
1,避免通配规则: 14
1.3.2 关键选择符 14
七、使用外部的JavaScript和CSS 15
八、减少DNS查询 15
九、避免重定向 16


一、减少HTTP请求

1.1使用图片地图:
假设导航栏上有五幅图片,点击每张图片都会进入一个链接,这样五张导航的图片在加载时会产生5个HTTP请求。然而,使用一个图片地图可以提高效率,这样就只需要一个HTTP请求。
服务器端图片地图:将所有点击提交到同一个url,同时提交用户点击的x、y坐标,服务器端根据坐标映射响应
客户端图片地图:直接将点击映射到操作

 <img src="planets.jpg" border="0" usemap="#planetmap" alt="Planets" />
<map name="planetmap" id="planetmap">
     <area shape="rect" coords="180,139,14" href ="venus.html" alt="Venus" />
     <area shape="rect" coords="129,161,10,10" href ="mercur.html" alt="Mercury" />
     <area shape="rect" coords="0,0,110,260" href ="sun.html" alt="Sun" />
     <area shape="rect" coords="140,0,110,260" href ="star.html" alt="Sun" />
</map>

1.2 CSS Sprites :
通过css中background-position来定位背景图。把多张图片组合成一个单一的形象。整体大小差不多,但减少HTTP请求的数量加快页面,当然合成图片大小也不能太大,不然也会事与愿违。
可能大家会认为合并后的图片会比分离图片的总和要大,因为还有可能会附加空白区域。实际上,合并后的图片会比分离的图片总和要小,因为它降低了图片自身的开销,譬如颜色表、格式信息等。

1.3 字体图标 :
在可以大量使用字体图标的地方我们可以尽可能使用字体图标,字体图标可以减少很多图片的使用,从而减少http请求,字体图标还可以通过CSS来设置颜色、大小等样式,何乐而不为。

1.4 合并脚本和样式 :
将多个样式表或者脚本文件合并到一个文件中,可以减少HTTP请求的数量从而缩短效应时间。然而合并所有的样式文件或者脚本文件可能会导致在一个页面加载时加载了多于自己所需要的样式或者脚本,对于只访问该网站一个(或几个)页面的人来说反而增加了下载量,所以大家应该自己权衡利弊。

二、使用CDN

如果应用程序web服务器离用户更近,那么一个HTTP请求的响应时间将缩短。另一方面,如果组件web服务器离用户更近,则多个HTTP请求的响应时间将缩短。
现在产品线就有用到cdn去处理内容等数据,但也暴露出了很多使用cdn的弊端:
1,不能直接控制组件服务器。
2,产品质量受cdn服务质量影响比较大。
3,响应时间可能会受到其他网站流量的影响。CDN服务提供商在其所有客户之间共享Web服务器组。
总之各有利弊,需要自行权衡是否适合各产品。

三、合理使用缓存

1.1添加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头

1.2 使Ajax可缓存
Ajax的目地是为突破web本质的开始—停止交互方式,向用户显示一个白屏后重绘整个页面不是一种好的用户体验。
POST的请求:是不可以在客户端缓存的,每次请求都需要发送给服务器进行处理,每次都会返回状态码200。(可以在服务器端对数据进行缓存,以便提高处理速度)
GET的请求:是可以(而且默认)在客户端进行缓存的,除非指定了不同的地址,否则同一个地址的AJAX请求,不会重复在服务器执行,而是返回304。
Ajax请求使用缓存
在进行Ajax请求的时候,可以选择尽量使用get方法,这样可以使用客户端的缓存,提高请求速度。

1.3 注意控制Cookie大小和污染
因为Cookie是本地的磁盘文件,每次浏览器都会去读取相应的Cookie,所以建议去除不必要的Coockie,使Coockie体积尽量小以减少对用户响应的影响;使用Cookie跨域操作时注意在适应级别的域名上设置coockie以便使子域名不受其影响。Cookie是有生命周期的,所以请注意设置合理的过期时间,合理地Expire时间和不要过早去清除coockie,都会改善用户的响应时间。

四、压缩组件

1.1合理切图
图片切图尽量少和小,按所需的规格来切。切完后的图片文件不能太大,必须要压缩,且尽可能的压缩到最小。有些图片可以放在背景上的建议就放在背景上。

1.2 压缩减小css及js文件体积
现在开源的压缩工具(比如:Closure Compiler)比较多,压缩可以适量降低css和js的文件体积,提升性能,当然还需要浏览器支持。
从HTTP1.1开始,Web客户端可以通过HTTP请求中的Accept-Encoding头来表示对压缩的支持
Accept-Encoding: gzip,deflate
如果Web服务器看到请求中有这个头,就会使用客户端列出来的方法中的一种来进行压缩。Web服务器通过响应中的Content-Encoding来通知 Web客户端。
Content-Encoding: gzip

代理缓存
当浏览器通过代理来发送请求时,情况会不一样。假设针对某个URL发送到代理的第一个请求来自于一个不支持gzip的浏览器。这是代理的第一个请求,缓存为空。代理将请求转发给服务器。此时响应是未压缩的,代理缓存同时发送给浏览器。现在,假设到达代理的请求是同一个url,来自于一个支持gzip的浏览器。代理会使用缓存中未压缩的内容进行响应,从而失去了压缩的机会。相反,如果第一个浏览器支持gzip,第二个不支持,你们代理缓存中的压缩版本将会提供给后续的浏览器,而不管它们是否支持gzip。
解决办法:在web服务器的响应中添加vary头Web服务器可以告诉代理根据一个或多个请求头来改变缓存的响应。因为压缩的决定是基于Accept-Encoding请求头的,因此需要在vary响应头中包含Accept-Encoding。
vary:Accept-Encoding

五、对js的优化

1.1放置位置 (页面底部)
脚本放在底部对于实际页面加载的时间并不能造成太大影响,但是这会减少页面首屏出现的时间,使页面内容逐步呈现。
js的下载和执行会阻塞Dom树的构建(严谨地说是中断了Dom树的更新),所以script标签放在首屏范围内的HTML代码段里会截断首屏的内容。
下载脚本时并行下载是被禁用的——即使使用了不同的主机名,也不会启用其他的下载。因为脚本可能修改页面内容,因此浏览器会等待;另外,也是为了保证脚本能够按照正确的顺序执行,因为后面的脚本可能与前面的脚本存在依赖关系,不按照顺序执行可能会产生错误。

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

混淆
混淆是应用在源代码上的另外一种方式,它会移除注释和空白符,同时它还会改写代码。在混淆的时候,函数和变量名将会被转换成更短的字符串,这时代码会更加精炼同时难以阅读。通常这样做是为了增加对代码进行反向工程的难度,这也同时提高了性能。

缺点:
混淆本身比较复杂,可能会引入错误。
需要对不能改变的符号做标记,防止JavaScript符号(譬如关键字、保留字)被修改。
混淆会使代码难以阅读,这使得在产品环境中调试问题更加困难。

注:就算使用gzip等方式来压缩文件,精简代码依然是有必要的。一般来说,压缩产生的节省是高于精简的,在生产环境中,精简和压缩同时使用能够最大限度的获得更多的节省。

例:
这里写图片描述
jquery-2.0.3.js:
这里写图片描述

jquery-2.0.3.min.js:
这里写图片描述
以上分别是jquery-2.0.3的学习版(未精简)和精简版,可见精简文件的大小比源文件小了155k,而且,在精简版中jquery还做了混淆,譬如用e代替window等,从而获得最大的节省。

1.3 JavaScript代码优化
1.3.1 浅谈JavaScript作用域
当JavaScript代码执行时,JavaScript引擎会创建一个执行环境,又叫执行上下文。执行环境定义了变量或函数有权访问的其他数据,决定了它们的行为,每个执行环境都有一个与它关联的变量对象,环境中定义的所有函数、变量都保存在这个对象中。
当代码在一个执行环境中执行时,JavaScript引擎会创建变量对象的一个作用域链,它可以保证对执行环境有权访问的变量和函数的有序访问 。当需要查找某个变量或函数时,JavaScript引擎会通过执行环境的作用域链来查找变量和函数,从作用域链的顶端开始,如果没找到,则向下寻找直至找到为止。若一直到全局作用域都没有找到,则该变量或函数为undefined。

例子:
function add(a,b) {
    return a + b;
}
var result = add(2,3);

例子示意图

1.3.2 使用局部变量
了解了作用域链的概念,我们应该知道在查找变量会从作用域链的顶端开始一层一层的向下找。显然,查找的层数越多,花费的时间越多。所以为了提高查找的速度,我们应该尽量使用 局部变量(到目前为止,局部变量是JavaScript中读写最快的标识符)。

例子:
function createEle() {
    document.createElement("div");
}
function createEle() {
    var doc = document;
    doc.createElement("div");
}

当document使用次数比较少时,可能无所谓,可是如果在一个函数的循环中大量使用document,我们可以提前将document变成局部变量。

来看看jquery怎么写的:

(function(window, undefined) {
     var jQuery = function() {}
     // ...
     window.jQuery = window.$ = jQuery;
})(window);

这样写的优势:
1、window和undefined都是为了减少变量查找所经过的scope作用域。当window通过传递给闭包内部之后,在闭包内部使用它的时候,可以把它当成一个局部变量,显然比原先在window scope下查找的时候要快一些。(原来的window处于作用域链的最顶端,查找速度慢)
2、undefined也是JavaScript中的全局属性。将undefined作为参数传递给闭包,因为没给它传递值,它的值就是undefined,这样闭包内部在使用它的时候就可以把它当做局部变量使用,从而提高查找速度。undefined并不是JavaScript的保留字或者关键字。
3、undefined在某些低版本的浏览器(例如IE8、IE7)中值是可以被修改的(在ECMAScript3中,undefined是可读/写的变量,可以给它赋任意值,这个错误在ECMAScript5中做了修正),将undefined作为参数并且不给它传值可以防止因undefined的值被修改而产生的错误。
1.3.3 避免增长作用域链
在JavaScript中,有两种语句可以临时增加作用域链:with、try-catch
with可以使对象的属性可以像全局变量来使用,它实际上是将一个新的变量对象添加到执行环境作用域的顶部,这个变量对象包含了指定对象的所有属性,因此可以直接访问。这样看似很方便,但是增长了作用域链,原来函数中的局部变量不在处于作用域链的顶端,因此在访问这些变量的时候要查找到第二层才能找到它。当with语句块之行结束后,作用域链将回到原来的状态。鉴于with的这个缺点,所以不推荐使用。
try-catch中的catch从句和with类似,也是在作用域链的顶端增加了一个对象,该对象包含了由catch指定命名的异常对象。但是因为catch语句只有在放生错误的时候才执行,因此影响比较少。
1.3.4 优化字符串连接
由于字符串是不可变的,所以在进行字符串连接时,需要创建临时字符串。频繁创建、销毁临时字符串会导致性能低下。
建议:使用join方法来代替字符串连接。

例子:
var temp = [];
var i = 0;
temp[i++] = "Hello";
temp[i++] = " ";
temp[i++] ="everyone";
var outcome = temp.join("");
注:在高版本的浏览器中好像处理了这种效率低下的情况。

1.3.5 条件判断优化
1,当条件分支比较多时,我们可以斟酌哪种条件出现的概率比较大,并将对应的语句放在最上面,这样可以减少判断次数。
2,使用switch语句,新版的浏览器基本上都对switch做了优化,这样层数比较深时,性能比if会更好
3,使用数组:

var v = [v0,v1,v2,v3,v4];
return v[valeue];
要求:对应的结果是单一值,而不是一系列操作

4,其他方面的优化

if(condition1) {
    return v1;
}
else {
    return v2
}
// 改成
if(condition1) {
    return v1;
}
return v2;

1.3.6 优化循环
1、循环总次数使用局部变量

for( var i = 0;i < arr.length;i++) {}
// 改成
var len = arr.length;
for( var i = 0;i < len;i++) {}

这样就避免了每次循环的属性查找。

2、如果可以,递减代替递增

for(var i = 0;i < arr.length;i++) {}
// 改成
for(var i = arr.length1;i>=0;i--) {}

3、展开循环

例如:
var i = arr.length - 1;
while(i--) 
{
    dosomething(arr[i]);
}

遇到这样的情况时,执行一次循环的时候我们可以选择不止执行一次函数。

var interations = Math.floor(arr.length / 8);
var left = arr.length % 8;
var  i = 0;
if(left) {
    do {
        dosomething(arr[i++]);
    } while(--left);
}
do {
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
    dosomething(arr[i++]);
} while(--interations);

当遇到大数组,减少循环的开销,性能不就提上去了嘛。(至于为什么是每次循环,调8次函数,大牛测出来的,这样达到最佳)

4、高效存取数据
JavaScript中4种地方可以存取数据:
字面量值;变量;数组元素;对象属性
字面量值和变量中存取数据是最快的,从数组元素和对象属性中存取数据相对较慢,并且随着深度增加,存取速度会越来越慢,譬如obj.item.value就比obj.item慢。
某些情况下我们可以将对象、数组属性存成局部变量来提高速度,譬如:

子1for( var i = 0;i < arr.length;i++){}
// 改成
var len = arr.length;
for( var i = 0;i < len;i++){}
子2var divList = document.getElementsByTagName("div");
for( var i = 0;i < divList.length;i++){}
// 改成 
var divList = document.getElementsByTagName("div");
for( var i = 0,len = divList.length;i < len;i++){}

1.3.7 事件委托优化
事件委托就是利用冒泡的原理,将原本应该添加在某些元素身上的监听事件,添加到其父元素身上,来达到提高性能的效果。

例子:
<div>
    <ul>
        <li>1</li>
        <li>2</li>
        //...
        <li>10</li>
    </ul>
</div>
<script>
window.onload = function() 
{
    var ul = document.getElementsByTagName('ul')[0];
    var liList = document.getElementsByTagName('li');
    for(var i = 0,len = liList.length;i < len;i++) 
{
        liList[i].onclick = function() 
       {
         alert(this.innerHTML);
        }
    }
}
</script>

这样我们就为每个li添加了监听事件了。
显然,我们通过循环为每个li添加监听事件是不优化的。这样不仅浪费了内存,在新的li加入的时候我们还要重新为它添加监听事件。

推荐写法:

<div>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
        <li>10</li>
    </ul>
</div>
<script>
window.onload = function() {
    var ul = document.getElementsByTagName('ul')[0];
    var liList = document.getElementsByTagName('li');
    ul.onclick = function(e) {
        var e = e || window.event;
        var target = e.target || e.srcElement;

        if(target.nodeName.toLowerCase() == "li") {
            alert(target.innerHTML);
        }
    }
}
</script>

这样写的好处:
只添加一个监听事件,节省了内存;新加入li的时候我们也不用为它单独添加监听事件;在页面中添加事件处理程序所需的时候更少,因为我们只需要为一个DOM元素添加事件处理程序。

六、对css的优化

1.1放置位置 (页面头部)
首先说明一下,将样式表放在头部对于实际页面加载的时间并不能造成太大影响,但是这会减少页面首屏出现的时间,使页面内容逐步呈现,改善用户体验,防止“白屏”。我们总是希望页面能够尽快显示内容,为用户提供可视化的回馈,这对网速慢的用户来说是很重要的。
将样式表放在文档底部会阻止浏览器中的内容逐步出现。为了避免当样式变化时重绘页面元素,浏览器会阻塞内容逐步呈现,造成“白屏”。这源自浏览器的行为:如果样式表仍在加载,构建呈现树就是一种浪费,因为所有样式表加载解析完毕之前无需绘制任何东西
1.2 css的精简
CSS的精简带来的节省一般来说是小于JavaScript精简的,因为CSS中注释和空白相对较少。所以建议通过合理优化来达到目的:
1、合并相同类
2、移除不使用的类
3、使用缩写

例子:
    .right {
            color: #fff;
            padding-top: 0;
            margin: 0 10px;
            border: 1px solid #111
    }
    .wrong {
            color: #ffffff;
            padding-top: 0px; 
            margin-top: 0;
            margin-left: 10px;
            border-color: #111;
            border-width: 1px;
            border-style: solid;
    }
上面.right是正确的的写法,颜色使用缩写,使用0代替0px,合并可以合并的样式。另外,在精简的时候其实样式最后一行的';'也是可以省略的。 

1.3 css选择器优化
1.3.1 了解浏览器的匹配规则
浏览器时从右向左匹配选择符的 。

例子:#abc > a
先查找页面中的所有a标签,在看它的父元素是不是id为abc
很明显这种写法就很低效了。

1,避免通配规则:
除了 * 之外,还包括子选择器、后台选择器等。
而它们之间的组合更加逆天,譬如:li *
浏览器会查找页面的所有元素,然后一层一层地寻找他的祖先,看是不是li,这对可能极大地损耗性能。
2,不限定ID选择器
ID就是唯一的,不要写成类似div#nav这样,没必要。
3,不限定class选择器
我们可以进一步细化类名,譬如li.nav 写成 nav-item
4,尽量避免使用后代选择器
通常后代选择器是开销最高的,如果可以,请使用子选择器代替。
5,替换子类选择器
如果可以,用类选择器代替子选择器,譬如
nav > li 改成 .nav-item
6,依靠继承
了解那些属性可以依靠继承得来,从而避免重复设定规则。
1.3.2 关键选择符
选择器中最右边的选择符成为关键选择符,它对浏览器执行的工作量起主要影响。
例子:
div div li span.class-special
乍一看,各种后代选择器组合,性能肯定不能忍。其实仔细一想,浏览器从右向左匹配,如果页面中span.class-special的元素只有一个的话,那影响并不大啊。

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

七、使用外部的JavaScript和CSS

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

影响因素:

1、每个用户产生的页面浏览量越少,内联脚本和样式的论据越强势。譬如一个用户每个月只访问你的网站一两次,那么这种情况下内联将会更好。而如果该用户能够产生很多页面浏览量,那么缓存的样式和脚本将会极大减少下载的时间,提交页面加载速度。
2、如果你的网站不同的页面之间使用的组件大致相同,那么使用外部文件可以提高这些组件的重用率。

八、减少DNS查询

当我们敲击一个网站(例如:www.baidu.com)会经过以下步骤:
域名解析 –> 发起TCP的3次握手 –> 建立TCP连接后发起http请求 –>
服务器响应http请求,浏览器得到html代码 –>
浏览器解析html代码,并请求html代码中的资源(如js、css、图片等) –>
浏览器对页面进行渲染呈现给用户
域名解析就是第一步,具体的域名又是如何解析的呢:

1,浏览器搜索自身dns缓存,如果没有就执行2 2,搜索操作系统中的dns缓存,如果没有就执行3
3,尝试着读取hosts文件,如果没有就执行4
4,浏览器就会发起一个DNS的系统调用,就会向本地配置的首选DNS服务器发起域名解析请求,获取的对应的地址。(一般情况执行到这基本都可以获取到)

    因此DNS也是开销,通常浏览器查找一个给定域名的IP地址要花费20~120毫秒,在完成域名解析之前,浏览器不能从服务器加载到任何东西。 
    当客户端DNS缓存(浏览器和操作系统)缓存为空时,DNS查找的数量与要加载的Web页面中唯一主机名的数量相同,包括页面URL、脚本、样式表、图片、Flash对象等的主机名。减少主机名的 数量就可以减少DNS查找的数量。
    减少唯一主机名的数量会潜在减少页面中并行下载的数量(HTTP 1.1规范建议从每个主机名并行下载两个组件,但实际上可以多个),这样减少主机名和并行下载的方案会产生矛盾,需要大家自己权衡。建议将组件放到至少两个但不多于4个主机名下,减少DNS查找的同时也允许高度并行下载。

九、避免重定向

当页面发生了重定向,就会延迟整个HTML文档的传输。在HTML文档到达之前,页面中不会呈现任何东西,也没有任何组件会被下载。所以应该尽量避免重定向。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值