前端性能优化:网站性能优化

网站性能优化可以从下面总结点入手。

1. 减少HTTP请求

  • 使用雪碧图 – CSS Sprites,把多个图片合并到一个单独的图片中,利用CSS –background-position调整图片显示位置。这种方式适用面比较广泛。
    缺点是,如果一张小图,需要N个颜色,就必须做N个不同颜色的小图,合并到大图里面。
  • 使用data:URL展示图片,它可以在页面中渲染图片但无需额外的HTTP请求,请求格式:

<img scr="data:image/jpg;base64, xxxxxxxxxxxxxxxx">

 

缺点是:此方案不适合mobile应用;IE7以下不支持;如果一张图片在多个页面被用到,无法利用浏览器缓存。
为了解决无法缓存问题,可以将data:image应用到CSS样式中,比如:

.imageA {
 background-image: url(data:image/jpg;base64, xxxxxxxxxxxxxxxx);
}

 

  • 合并脚本和样式表
  • Multipart XHR
    运行客户端用一个HTTP请求就可以从服务端传递多个资源。它通过在服务端将资源(CSS文件,HTML片段,Javascript代码或者base64编码的图片)打包成一个由双方约定的字符串分割的长字符串,并发送到客户端。
    然后用Javascript代码处理这个长字符串,并根据他的mime-type类型和传入的其他‘头信息’解析出每个资源。
    例如,解析一串图片编码,输入为req.responseText

function splitImages(imageString){
         var imageData = imageString.split('\u0001');
         var imageElement;

         for (var i =0, len = imageData.length; i<len; i++){
           imageElement = document.createElement('img');
           imageElement.src = 'data:image/jpeg;base64,' + imageData[i];
           document.getElementById('container').appendChild(imageElement);
         }
}

 

2. 使用CDN

内容发布网络(CDN)是一组分布在多个不同地理位置的WEB服务器,用于更加有效地向用户发布内容。
CDN用于发布静态内容,如图片,脚本,样式表和Flash。

不使用CDN时:

  1. 用户在浏览器访问栏中输入要访问的域名。
  2. 浏览器向DNS服务器请求对该域名的解析。
  3. DNS服务器返回该域名的IP地址给浏览器。
  4. 浏览器使用该IP地址向服务器请求内容。
  5. 服务器将用户请求的内容返回给浏览器。

尽量将CDN的域名设置的不同于请求方网站的域名。比如,网站为a.com,CDN域名可以设置为acdn.com。为什么呢?

  • Cookie隔离:Cookie 是紧跟域名的,同一个域名下的所有请求,都会携带 Cookie。试想,海量请求图片或JS/CSS文件时,还要携带Cookie,也会成为不小的开销。

  • 并且,浏览器在同一个时刻向同一个域名请求文件的并行下载数量是有限的(Chrome为6个并发),所以,可以利用多个域名主机存放不同的静态资源,增大页面加载时资源并行下载数量。

3. 利用HTTP缓存

具体内容参考文章 

//www.cnblogs.com/yizhiamumu/p/16687989.html

4. 压缩组件

开启HTTP Gzip压缩。

request: Accept-Encoding: gzip, deflate
response:Content-Encoding:gzip

 

5. 将样式表放在顶部

外部脚本文件和CSS文件是并行下载的,把样式表在页面中的位置并不影响下载时间,但会影响页面的呈现!浏览器必须要等样式表加载完毕之后才渲染页面
因此,应该把样式表放在head中,这样它就能被最先下载使页面逐步呈现。

6. 将JS脚本放在底部

一般,JS脚本是被禁止并行下载的,因为JS脚本可能使用document.write来修改页面内容,所以必须保证JS执行顺序。
脚本下载后,必须执行完,才可以继续后面的解析。

但是,Chrome浏览器支持并发下载资源文件,并保证按顺序执行(参考《WebKit技术内幕-朱永盛》)。

7. 避免CSS表达式

CSS表达式是动态设置CSS属性的一种强大(并且危险)的方式。CSS表达式求值频率比人们期望的要高,它们不只在页面呈现和大小变化时求知,甚至用户鼠标在页面上拖拽都要求知。
如,将背景色设置为每小时变化一次:

background-color:expression((new Date()).getHours()%2?"#ccc":"#000");

 

触发频率太高!不建议使用。

8. 使用外部JS和CSS

纯粹来讲,内联的JS和CSS可以产生比外部文件文件更快的响应速度。
但是现实中,外部链接的JS和CSS文件会产生较快的页面,是因为JS和CSS文件有可能被缓存

9. 减少DNS查找

DNS也是开销。通常浏览器查找一个给定主机名的IP地址要花费20~120毫秒。在DNS查找完成之前,浏览器不能从主机名哪里下载任何东西。
只要cline-server之间保持TCP连接打开状态,就无需DNS查找。所以,我们可以通过使用Keep-Alive和较少的域名来减少DNS查找。

Keep-Alive,HTTP1.1协议中推出的持久连接。特点为:只要任意一端没有明确提出断开连接,则保持TCP连接状态。

含有Keep-Alive首部的response示例:

HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Thu, 11 Aug 2016 15:23:13 GMT
Keep-Alive: timeout=5, max=1000
Last-Modified: Mon, 25 Jul 2016 04:32:39 GMT
Server: Apache

 

10. 压缩JavaScript和CSS

可以用各类构建或者编译工具压缩脚本和样式文件,比如:gulpwebpack

11. 少用iframe

iframe是开销最高的DOM元素,它的缺点远大于优点。

  • 不利于SEO:搜索引擎的检索程序无法解读iframe中的src
  • 阻塞onload事件:iframe不加载完毕,就不会触发父窗口的onload事件。
  • 影响页面资源并行加载:iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面资源的并行加载。
    为了解决两个问题,可以动态设置iframe中的src属性,代码如下:

<iframe id="iframe1" src=""></iframe>

<script>
document.getElementById('iframe1').src = "www.api.a.com";
</script>

 

12.少用Table

table内容渲染是将table的DOM渲染树全部生成完并一次绘制到页面上,所以,在渲染长表格时很耗性能,应该尽量避免使用。

可以使用uldiv替代。

13. JS文件异步/按需加载

有多种方式支持JavaScript异步加载。

  • Script DOM Element
    这恐怕是最常见的异步加载脚本方法,即,动态创建一个script标签,并设置其src值。如下:

function createScript(url){
  var scrElem = document.createElement('script');
  srcElem.src = url;
  document.getElementsByTagName('head')[0].appendChild(scrElem);
}

 

优点:支持跨域加载脚本文件;兼容性最好、普适性最高的方案
缺点:脚本无序执行;会阻塞onload事件

  • XMLHttpRequest

通过XMLHttpRequest的方式下载脚本文件,然后使用eval或者动态添加<script>标签并设置其text属性来执行脚本。

// 不考虑IE
var xhrObj = new XMLHttpRequest();
xhrObj .onreadystatechange = function(){
  if (xhrObj .readyState == 4) {
    // 方式一
    eval(xhrObj.responseText);  
    // 方式二
    var scrElem = document.createElement('script');
    srcElem.text= xhrObj.responseText;
    document.getElementsByTagName('head')[0].appendChild(scrElem);
  }
}
xhrObj .open('GET', 'a.js', true);
xhrObj .send('');

 

优点:将脚本下载和脚本执行分离开,可以在适当的时候再执行脚本;不会阻塞onload事件
缺点;通过XMLHttpRequest获取的脚本文件必须和主页面是同一个域名下。也就是说,不支持跨域下载脚本(除非做跨域处理)。因此不适合加载第三方文件;脚本无序执行。

  • defer和async

两者都支持异步加载文件,不同之处是,defer会在全部资源下载完毕后才执行JS文件;async在脚本文件下载完就立刻执行,并且,async模式加载的JS文件无法依序执行,对于有顺序依赖的脚本来说,不应该采用这种方式。
defer相对友好一些,并可以保证JS文件按照顺序执行

<script src="a.js" defer></script>
<script src="a.js" async></script>

 

defer和async优点:支持跨域加载脚本文件。
defer优点:可以保证JS文件按照顺序执行。

defer和async缺点:IE10以上(包括IE10)才支持。
async缺点:JS文件无法依序执行;会阻塞onload事件

14. 图片懒加载

通过图片懒加载可以让一些不可视的图片不去加载,避免一次性加载过多的图片导致请求阻塞(浏览器一般对同一域名下的并发请求的连接数有限制),这样就可以提高网站的加载速度,提高用户体验。

实现方案一

第一步: 懒加载的img标签的src设置缩略图或者不设置src,然后自定义一个属性,值为真正的图片或者原图的地址(比如data-src)。

// //a.com/logo.png 是图片的真实地址,设置到data-src属性上。
<img data-src="//a.com/logo.png" class="lazy-image"/> 
// css部分 
.lazy-image { 
    background: url('loading.gif') no-repeat center; 
} 

 

第二步:页面加载完后,获取所有需要懒加载的图片的元素集合,判断是否在可视区域,如果是在可视区域的话,设置元素的src属性值为真正图片的地址。

// 监听滚动事件
document.addEventListener('scroll', inViewShow);
// 显示图片
inViewShow() {     
    let imageElements = Array.prototype.slice.call(document.querySelectorAll('.lazy-image'))    
    let len = imageElements.length     
    for(let i = 0; i < len; i++) {         
        let imageElement = imageElements[i]        
        const rect = imageElement.getBoundingClientRect() // 出现在视野的时候加载图片         
        if(rect.top < document.documentElement.clientHeight) {             
            imageElement.src = imageElement.dataset.src // 赋值到真正的src上           
            imageElements.splice(i, 1)             
            len--             
            i--         
        }     
    } 
}

 

实现方案二

利用浏览器新特性IntersectionObserver

IntersectionObserver接口提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法)。

// 渲染
public render() {
    const { src } = this.state
    // 一旦src变化,更新为img标签,加载图片
    if (src) {
      return <img {...this.props} src={src} />
    }
   // 占位图片
    return (
      <span ref={(ele) => (this.ele = ele)}  />
    )
  }

// 监控
public componentDidMount() {
    // 判断浏览器是否支持IntersectionObserver函数
    if (typeof IntersectionObserver === 'function') {
      const cb = (entries) => {
        if (entries.some((item) => item.intersectionRatio > 0)) {
          const { src } = this.props
          this.setState({ src })
          this.ob.disconnect() // 取消监听
        }
      }
      this.ob = new IntersectionObserver(cb, {
        root: document.body, // 祖先元素
        threshold: [0, 0.01], // 交叉值
      })
      this.ob.observe(this.ele)
    } else {
      const { src } = this.props
      this.setState({ src })
    }
 }

 

实现方案三

使用三方库处理图片懒加载。

15. 避免页面中空的href和src

link标签中的href,或者iframscriptimg标签的src属性为空时,浏览器在渲染过程中仍然会将hrefsrc中的空内容进行加载,直到失败为止。这样会阻塞页面中其他资源的下载过程。

16. 减少页面重定向

页面重定向会延长页面内容返回的等待时间,一次重定向大致需要600毫秒。

17. 使用HTTP2.0

相比HTTP1.1,2.0版本有了更强大的能力,可以提升传输性能。

  • TCP多路复用能力:这和HTTP1.1的连接复用还是不同的。HTTP1.1的连接复用(keep-alive)为应用层,指HTTP复用TCP连接后,可以串行发送多个HTTP请求。而HTTP2.0多路复用在传输层,帧的多路复用,即不同文件的传输帧可以在一个TCP连接中同时传输,也就是多个HTTP请求并行发送。
  • 采用二进制格式传输数据,非HTTP 1.x的文本格式(默认)。且采用HPACK压缩传输,最大限度节省了宽带。
  • 支持传输流的优先级和流量控制:服务端可以优先输出CSS文件,而不必依赖HTML中定义的脚本加载顺序。
 
image.png

18. 预加载dns-prefetchpreconnectprefetchprerender

dns-prefetch:使浏览器主动去执行域名解析。

<link rel="dns-prefetch" href="//fonts.google.com/" >

 

preconnect:提前建立连接。比dns-prefetch多走了两步,除了完成dns解析之外,还完成了TCP握手(HTTPS下还完成TLS握手)。

<link rel="preconnect" href="//fonts.google.com/" >

  

prefetch:让浏览器预加载一个资源(HTML,JS,CSS或者图片等),可以让用户跳转到其他页面时,响应速度更快。

<link rel="prefetch" href="//css-tricks.com/a.png">

  

prerender:不仅会加载资源,还会解执行页面,进行预渲染(太重,不建议使用)

<link rel="prefetch" href="//css-tricks.com/a.html">