前端性能优化的建议

·  阅读 646

1.为什么要做性能优化?

随着互联网的飞速发展,用户对网页响应速度的要求越来越高,移动端则更是如此。性能的好坏直接影响用户的体验。对于一个前端工程师,如何能够让页面加载更快,提高用户体验,如何减少请求所占带宽,降低服务器压力,是我们工作中必须要思考的。

2.从输入URL到页面加载

首先我们有必要了解下,在浏览器中输入url,到页面加载完成的整个链路。 image.png

1. DNS(域名系统)解析

输入URL后,需要找到这个URL对应的服务器,从而加载 HTML。为了查询服务器地址,第一步需要通过DNS将域名解析成IP地址。在解析过程中,浏览器首先依次查询自身缓存,系统缓存和路由器缓存的记录,如果没有则查询本地host文件,直到拿到ip地址,再没有就向DNS服务器发送域名解析请求。

2. TCP连接

建立连接时,两端主机必须同步双方的初始序号,并且发送确认的ACK。此过程就是三次握手。 简单图解一下就是这么回事。

sequenceDiagram
客户端->>服务器: 嘿,我想建立连接,我的初始序列号送你
服务器-->>客户端: 好的呀,礼尚往来,我的序列号也送你,确认要连接嘛?
客户端-)服务器: 确认,那我们开始聊天吧!

构建TCP请求会增加大量的网络时延。

3. HTTP 请求

客户端将要发送的内容构建成HTTP请求报文并封装在TCP包中,通过TCP协议发送到服务器指定端口。

4. 服务端响应请求

服务器端接收到客户端的HTTP请求后,查找客户端请求的资源,返回相应的HTML文件。

5. 页面渲染

  • HTML文档被解析成一棵以document为根的DOM树,解析过程中如果遇到JavaScript,则会暂停解析并下载相应的文件造成阻塞。
  • 浏览器解析CSS,构建CSSOM树。
  • DOM树和CSSOM树融合成渲染树,然后浏览器确认页面各元素的位置。
  • 浏览器根据布局结果进行页面绘制和优化。

3.思路

了解了上述整个过程,就可以清晰看到,性能优化可以从两方面入手,一是http层的优化,二是渲染层的优化。现在已经有很多成熟可靠的工具和方法可以帮助进行优化,另外也要注意实际开发中,应优先考虑有助于性能的写法和实现方式。下面来具体介绍一下吧。

4.http层优化

1.DNS预解析

DNS预解析技术,就是让具有此属性的域名不需要用户点击链接就在后台解析缓存,由于域名解析和内容载入是串行的操作,这样用户在点击链接时就不用DNS解析了,减少用户的等待时间,提升用户体验。

// 用meta信息来告知浏览器, 当前页面要做DNS预解析
<meta http-equiv="x-dns-prefetch-control" content="on">
<link rel="dns-prefetch" href="www.baidu.com" />  
// 只有部分浏览器支持
复制代码

2.使用HTTP/2

  • HTTP/2 采用二进制格式传输数据,而非 HTTP 1.x 的文本格式,二进制协议解析起来更高效。
  • HTTP/2的多路复用代替原来的串行化单线程和阻塞机制,所有请求的都是通过一个 TCP 连接并发完成。 HTTP 1.x 中,如果想并发多个请求,必须使用多个 TCP 链接,且浏览器为了控制资源,还会对单个域名有 6-8个的TCP链接请求限制
  • HTTP/2可以在发送页面HTML时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应
  • HTTP/2对消息头进行压缩传输,能够节省消息头占用的网络流量。而HTTP/1.x每次请求,都会携带大量冗余头信息,浪费带宽资源。

image.png

image.png 根据上图的对比就可以发现HTTP/2的优势,资源同时加载,后面加载的资源不需要进行排队

3.减少http请求次数

下面看一个HTTP请求的例子。

image.png

从上图看出,真正下载数据的时间占比为 2.24 / 138.45 = 1.62%,文件越大,这个比例越大,如果将多个小文件合并为一个大文件,从而减少 HTTP 请求次数,那么将大大减少HTTP开销。

webpack 可以使用如下插件进行文件合并、打包和压缩,基本上能压缩50%以上。

  • JavaScript:UglifyPlugin
  • CSS :MiniCssExtractPlugin
  • HTML:HtmlWebpackPlugin

4. gzip压缩文件

压缩文件可以减少文件下载时间,让用户体验性更好。上面介绍了webpack几个插件压缩,其实还可以做得更好,就是gzip。gzip 是目前最有效的压缩方法。举个例子,使用Vue开发的项目构建后生成的 app.js 文件大小为 1.4MB,使用 gzip 压缩后只有 573KB,体积减少了将近 60%。

启用gzip需要客户端和服务端的支持。请求头中 accept-encoding:gzip 表示来标识客户端对gzip压缩的支持。在http响应头中 content-encoding:gzip,表示服务端使用了gzip的压缩方式。

前端配置:

// 安装
npm install compression-webpack-plugin --save-dev

// webpack配置
const CompressionWebpackPlugin = require('compression-webpack-plugin');
plugins.push(
    new CompressionWebpackPlugin({
        asset: '[path].gz[query]',// 目标文件名
        algorithm: 'gzip',// 使用gzip压缩
        test: new RegExp(
            '\\.(js|css)$' // 压缩 js 与 css
        ),
        threshold: 10240,// 资源文件大于10240B=10kB时会被压缩
        minRatio: 0.8 // 最小压缩比达到0.8时才会被压缩
    })
);
复制代码

5.图片优化

使用字体图标iconfont代替图片,字体图标是矢量图,不会失真,且生成的文件特别小。

6.浏览器缓存

浏览器缓存分为强缓存和协商缓存。

1.强缓存

强缓存就是发现有缓存直接用,利用Expires (HTTP/1.0) 和Cache-Control (HTTP/1.1) 这两个请求头字段,使请求的内容缓存下来,避免了必要的HTTP请求

Expires头的内容是一个时间值,表示资源在本地的过期时间。在过期时间内,就直接使用缓存的资源,不会发送HTTP请求。Cache-Control(用的比较多)的作用也是类似的。

2.协商缓存

协商缓存就是先询问服务器缓存是否可以用,再判断是否用缓存

  • 浏览器第一次请求,服务器在respone的header加上Last-Modified(最后修改时间)
  • 浏览器再次请求,request的header上会加上If-Modified-Since,该值为缓存之前返回的Last-Modify
  • 服务端拿到这两个值对比,如果相等的话,则命中缓存,返回304 Not Modified,但是不会返回资源内容,否则, 如果 Last-Modify > if-Modify-Since, 则会给出200响应,从服务器加载资源,并且更新Last-Modify为新的值。

理解http浏览器缓存

5.浏览器渲染层优化

1.避免js阻塞

由于JS会阻塞浏览器渲染,一般将js放在在<body>标签的底部,css放在<head>标签内,防止出现白屏现象。

2.减少重绘和回流

什么操作会导致回流?

  • 添加或者删除可见的DOM元素
  • 元素位置改变
  • 元素尺寸改变(边距、填充、边框、宽度和高度)
  • 内容改变(比如文本改变或者图片大小改变)而引起的计算值宽度和高度改变
  • 页面渲染初始化
  • 浏览器窗口尺寸改变——resize事件 回流需要花费大量的时间进行样式计算和节点重绘与渲染,所以应当尽量减少回流次数

什么操作会导致重绘?

  • 元素的属性或者样式发生变化,但不影响布局

如何减少重绘回流?

  1. 不要使用table布局,因为一个小改动可能会造成整个table重新布局。而且table渲染比较慢。
  2. 元素定义高度或最小高度,否则元素的动态内容载入时,会导致页面位置改变,造成回流。
  3. 用 Js 修改样式时,不要直接写样式,而是切换 class 来批量改变样式。
  4. 使用DocumentFragment合并DOM插入。
  5. 缓存DOM对象
//没有缓存dom
 for (let i = 0; i < document.getElementsByTagName('p').length; i++) {
 //每一次循环都会去查找tagName为p的元素,效率自然非常低
 ...
}
//缓存dom
var p = document.getElementsByTagName('p');
for (let i = 0; i < p.length; i++) {
// 提高查找效率
}
复制代码

3.图片懒加载

对于图片很多的网站来说,一次性加载全部图片,会对用户体验影响很大,使用图片懒加载,不仅提升用户体验,还能节省用户流量。

创建一个自定义属性data-src存放真正需要显示的图片路径,而img自带的src放一张大小为1 * 1px的图片路径,当图片滚动到可视区域内,用js取到该图片的data-src的值赋给src。

判断是否在可视区域可以用getBoundingClientRect,也可以用offsetTop-scroolTop<=clientHeight判断

//html
<img src="img/loading.gif" alt="1" data-src="img/g1.jpg">
<img src="img/loading.gif" alt="2" data-src="img/g2.jpg">
<img src="img/loading.gif" alt="3" data-src="img/g3.jpg">

//js
<script>
var imgs = document.querySelectorAll('img');

//判断是否在可视区域内
function isInner(el) {
    var bound = el.getBoundingClientRect();
    var clientHeight = window.innerHeight;
    return bound.top <= clientHeight;
} 
function check() {
  imgs.forEach(function(el){
    if(!el.dataset.isLoaded && isInner(el)){
      //在可视区域,且没下载过,下载图像
       loadImg(el);
    }
  })
}
function loadImg(el) {
    var source = el.dataset.src;
    el.src = source;
    el.dataset.isLoaded = 1
}
window.onload = window.onscroll = function () {
   // 滚动触发
    check();
}
</script>
复制代码

4.缩略图

对于一些很大的又不常用的图片,可以用缩略图的方式展示,当用户鼠标悬浮在上面才展示全图。

5.响应式图片

浏览器根据不同分辨率显示不同大小图片,既保证显示效果,又能节省带宽,提高加载速度

<picture>
    <source srcset="banner_w1000.jpg" media="(min-width: 801px)">
    <source srcset="banner_w800.jpg" media="(max-width: 800px)">
    <img src="banner_w800.jpg" alt="">
</picture>
复制代码

阮一峰 响应式图片教程

6.事件代理

事件代理是指将事件监听器注册在父级元素上,由于子元素事件会通过事件冒泡向上传播到父节点,因此可以由父节点的监听函数统一处理多个子元素的事件。利用事件代理,可以减少内存使用,提高性能及降低代码复杂度。

7.事件节流

使用函数节流(throttle)或函数防抖(debounce),限制事件频繁触发。

  • 函数节流: 函数节流的应用场景一般是onrize,onscroll等这些频繁触发的函数,比如你想获取滚动条的位置,然后执行下一步动作,如果监听后执行的是Dom操作,这样的频繁触发执行,可能会影响到浏览器性能,甚至会将浏览器卡崩。所以我们可以规定多少秒执行一次,这种方法叫函数节流
//限制500ms执行一次
// 方式1
var type = false;
window.onscroll = function(){
   // 执行了一次后,500ms内再疯狂操作也没用
   // 直到500ms之后才能执行下一次
    if(type === true) return;
    type = true;
    setTimeout(()=>{
        console.log("要执行的事");
        type = false;
    },500)
}
//方式2
var time = null;
window.onscroll = function(){
    let curTime = new Date();
    if(time==null){
       time = curTime; 
    }
    // 判断两次操作的时间差是否小于500ms
    if((curTime-time)>500){
        console.log("要执行的事");
    }
}

复制代码
  • 函数防抖 在特定的时间内没有触发执行条件,最后才执行一次就是函数防抖,应用场景:频繁操作点赞和取消点赞,高频查询list等,需要获取最后一次操作结果并发送给服务器
var timer = null;
function click(){
  // 如果500ms内频繁操作,每次都会清除定时器再重新创建一个
  // 直到最后一次操作,500ms后执行
    clearTimeout(timer);
    timer = setTimeout(()=>{
        ajax(...);
    },500)
}
复制代码

函数防抖和节流

8.分页加载

对于数据量较大的列表,使用分页加载,减少服务器压力。

9.模块按需引用

在SPA等业务逻辑比较复杂的系统中,需要根据路由来加载当前页面需要的业务模块 ,按需引用,是一种很好的优化网页的方式。只在需要用到的时候进行加载相关模块,这种方式提高⾸屏加载速度,减轻了应用总体积。

webpack 提供了两种方法,优先选择import()语法,或使用require.ensure。

10.及时清理环境

消除对象引用,防止内存泄漏,清除定时器等。

6.检查性能

网站性能分为加载性能和运行性能

  1. 加载性能主要看白屏时间和首屏时间
  • 白屏时间:指从输入网址,到页面开始显示内容的时间。将以下脚本放在 </head> 前面就能获取白屏时间。
<script>
    new Date() - performance.timing.navigationStart
</script>
复制代码
  • 首屏时间:指从输入网址,到页面完全渲染的时间。 在 window.onload 事件里执行 new Date() - performance.timing.navigationStart 即可获取首屏时间
  1. 检查运行性能 在实际应用中,需要根据项目自身情况选择合适的优化方式,配合 chrome 的开发者工具的performance,可以查看网站在运行时的性能。

点击左上角的灰色圆点,然后模仿用户使用网站,在使用完毕后,点击 stop,就能得到网站运行期间的性能报告。如果哪个处理上占用大量时间,那么可以考虑此处的性能优化了。

分类:
前端
收藏成功!
已添加到「」, 点击更改