真的超级全的一篇前端性能优化清单

285次阅读  |  发布于3年以前

前言

当我们去面试的时候,很大概率会被面试官问这么一个问题:你有尝试过对项目做性能优化吗?或者你了解哪些性能优化的方法?听到这个问题的你可能是这样的:

似曾相识但又说不清楚,往往只能零散地说出那么几点,难以做到有条理的回答。那么,本文就带你简单了解前端性能优化的几个主要方面,旨在抛砖引玉。

一、资源的合并和压缩

web前端应用的开发与部署过程:

输入url到页面显示出来的过程:

请求过程中一些潜在的性能优化点:

总结:深入理解http请求的过程是前端性能优化的核心。

优化核心

google首页案例学习

1.html压缩

HTML代码压缩就是压缩一些在文本文件中有意义,但是在HTML中不显示的字符,包括空格,制表符,换行符等,还有一些其他意义的字符,如HTML注释也可以被压缩;

一个简单的计算:

google的流量,占到整个互联网的40%,预计2016年全球网络流量将达到1.3ZB(1ZB = 10^9TB),那么google2016年的流量就是1.3ZB * 40%,如果google1MB请求减少一个字节,每年可以节省流量近500TB流量。

如何进行html压缩

2.css代码压缩

分为两部分:

如何进行css压缩

3.js压缩与混乱(丑化)

包括:

JS压缩与混乱(丑化)

4.文件合并

文件合并的好处:

左边的表示使用http长链接keep-alive但不合并请求的情况,需要分三次去获取a.jsb.jsc.js;右边是使用长链接并且合并请求的情况,只需要发送一次获取合并文件a-b-c.js的请求,就能将三个文件都请求回来。

不合并请求有下列缺点:

文件合并存在的问题

使用建议

如何进行文件合并

二、图片相关的优化

有损压缩过程:

一张JPG图片的解析分别要进行:

最终得到JPEG-Compressed Image Data,即真正显示出来的JPG图片。虽然这是一种有损压缩,但是很多情况下,这些损失的数据并不影响显示;

png8/png24/png32之间的区别

不同格式图片常用的业务场景

1.图片压缩

针对真实图片情况,舍弃一些相对无关紧要的色彩信息,对图片进行压缩;比如在线压缩网站:https://tinypng.com/

2.css雪碧图

将网站上用到的一些图片整合到一张单独的图片中,从而减少网站HTTP请求数量。原理为:设定整张雪碧图可示区域,将想要显示的图标定位到该处(左上角);缺点:整合图片比较大时,一次加载比较慢。

如天猫的雪碧图:

很多情况下,并不是所有的小图标都放在一张雪碧图中,而是会适当进行拆分。现在使用雪碧图的场景比较少了。

自动生成雪碧图样式的网站:http://www.spritecow.com/

选中雪碧图中对应的图标,就会生成对应的样式。

3.网页内联图片(Image inline

将图片的内容内嵌到html当中,减少网站的HTTP请求数量,常用于处理小图标和背景图片。网页内联图片写法为:

<imgsrc="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA..."alt="" >

浏览器上的表现形式为:

这里提供一个将:imageDataUrI的网址:http://tool.c7sky.com/datauri/

缺点:

所以要根据场景使用,不过内联图片减少HTTP请求的优点还是很显著的。比如,在开发中小于4KB8KB的图片都会通过构建工具自动inlineHTML中,这种情况下Image inline带来的图片大小增长其实是比增加HTTP请求次数更优的。

4.矢量图SVGiconfont

使用iconfont解决icon问题

应尽量使用该方式,比如可以采用阿里巴巴矢量图库:

可以选择格式进行下载:

可以看到它们的大小有着明显的差异:

使用SVG进行矢量图的控制

SVG 意为可缩放矢量图形(Scalable Vector Graphics)。SVG 使用 XML 格式定义图像。下为示例:

在线转换网站:http://www.bejson.com/convert/image_to_svg/

5.webp

webp的优势体现在它具有更优的图像压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量;同时具备了无损和有损的压缩模式、Alpha透明以及动画的特性。在JPEGPNG上的转化效果都非常优秀、稳定和统一。安卓上不存在兼容性问题,推荐安卓下使用。

以下为淘宝网首页请求的图片:

可以看到,图片中大量地添加了webp格式的选择。.jpg_.webp表示当浏览器支持webp时采用webp格式,否则采用jpg格式。

下面为B站首页的图片,可以看到基本都采用了webp格式:

同一张图片jpg格式和webp格式压缩率有着明显的差异:

可以通过在线网站将图片转换为webp:https://zhitu.isux.us/

像图片这样的静态文件可以存放在CDN服务器上,让CDN服务器批量地将图片转换成Webp格式;

三、浏览器渲染引擎与阻塞

1.渲染的主要模块

版本一:

版本二:

一个渲染引擎主要包括:HTML解析器,CSS解析器,javascript引擎,布局layout模块,绘图模块:

2.渲染过程

浏览器渲染页面的整个过程:浏览器会从上到下解析文档。

2 . 浏览器解析时遇见 HTML 标记,就会调用HTML解析器解析为对应的 token (一个token就是一个标签文本的序列化)并构建 DOM 树(就是一块内存,保存着tokens,建立它们之间的关系)。在生成DOM的最开始阶段(应该是Bytescharacters后),并行发起css、图片、js的请求,无论他们是否在HEAD标签中。

注意:发起js文件的下载请求(request)并不需要DOM处理到那个script节点;

3 . 遇见 style/link 标记 调用解析器 处理 CSS 标记并构建 CSS样式树; 4 . 遇见 script 标记 调用 javascript解析器处理script标记,绑定事件、修改DOM树/CSS树等; 5 . 将 DOM树 与 CSS树 合并成一棵渲染树(Render Tree)。 6 . 布局(Layout):根据渲染树中各节点的样式和依赖关系,计算出每个节点在屏幕中的位置; 7 . 绘图(Painting):按照计算出来的结果:要显示的节点、节点的CSS与位置信息,通过显卡,把内容画到屏幕上;

经过第一次Painting之后DOMCSSOMRender Tree都可能会被多次更新,比如JS修改了DOM或者CSS属性时,LayoutPainting就会被重复执行。除了DOMCSSOM更新的原因外,图片下载完成后也需要调用LayoutPainting来更新网页。

补充:

1. HTML中可能会引入很多的css、js这样的外部资源,这些外部资源在浏览器端是并发加载的。但是浏览器会对同一域名进行并发数量(度)的限制,即单个域名的并发度是有限的;

2. 所以,经常将大部分的资源托管到CDN服务器上,并且设置3~4个CDN域名。防止只有一个CDN域名的情况下,达到了浏览器外部资源并发请求数目的上限,导致很多资源无法做到并发请求。所以,应设置多个CDN域名;

3.css阻塞

只有通过link引入的外部css才会产生阻塞:

4.js阻塞

5.总结

四、懒加载和预加载

1.懒加载

图片进入可视区域之后再请求图片资源的方式称为图片懒加载。适用于图片很多,页面很长的业务场景,比如电商;

懒加载的作用:

懒加载实现的原理:

监听onscroll事件,判断可视区域位置:

图片的加载是依赖于src路径的,首先可以为所有懒加载的静态资源添加自定义属性字段,用于存储真实的url。比如是图片的话,可以定义data-src属性存储真实的图片地址,src指向loading的图片或占位符。然后当资源进入视口的时候,才将src属性值替换成data-src中存放的真实url

<img src="" class="image-item" alt="" lazyload = "true" data-src="TB27YQvbm_I8KJjy0FoXXaFnVXa_!!400677031.jpg_180x180xzq90.jpg_.webp">

懒加载实例

可以使用元素的getBoundingRect().top来判断当前位置是否在视口内,也可以使用元素距离文档顶部的距离offsetTopscrollTop是否小于视口高度来判断:

举例

比如手机淘宝首页:

当快要滚动到需要展示的图片时才进行图片的请求,可以看到图片上有一个<span style="font-size: 14px;">lazyload的属性:

2.预加载

预加载与懒加载正好是相反的过程:懒加载实际上是延迟加载,将我们所需的静态资源加载时间延后;而预加载是将图片等静态资源在使用之前的提前请求,这样资源在使用到时能从缓存中直接加载,从而提升用户体验;

预加载的作用:

预加载的实例

例如九宫格抽奖业务,每个奖品都有一个选中态和非选中态,实际上这是由两张图片组合而成的。由于每个奖品的选中过程都是一瞬间,这就对图片的选中态和非选中态切换效率要求很高,如果选中态的图片没有预加载的话显然是来不及的。

所以,实际上对于九宫格中所有图片选中态的样式和对应的图片都需要进行预加载,从而让我们在抽奖的过程中,能够瞬间从缓存中读取到选中态的图片,从而不影响抽奖效果的展示。

除此之外还有网站登录或活动时需要用到的动画,这是在动画需要的每帧图片都完全预加载完之后才会进行显示的。

五、重绘与回流

1.CSS图层

浏览器在渲染一个页面时,会将页面分为很多个图层,图层有大有小,每个图层上有一个或多个节点。在渲染 DOM的时候,浏览器所做的工作实际上是:

1、获取DOM后分割为多个图层;

2、对每个图层的节点计算样式结果(Recalculate style--样式重计算);

3、为每个节点生成图形和位置(Layout--回流和重布局);

4、将每个节点绘制填充到图层位图中(Paint SetupPaint--重绘);

5、图层作为纹理上传至GUI

6、复合多个图层到页面上生成最终屏幕图像(Composive Layers--图层重组);

2.创建图层的条件

3.重绘(Repaint)

重绘是一个元素外观的改变所触发的浏览器行为,比如background-coloroutline等属性。这些属性不影响布局,只影响元素的外观,风格,会造成DOM元素的重新渲染,这个过程称为重绘。

需要注意的是:重绘是以图层为单位,如果图层中某个元素需要重绘,那么整个图层都需要重绘。比如一个图层包含很多节点,其中有个gif图,gif图的每一帧,都会重回整个图层的其他节点,然后生成最终的图层位图。

因此,可以通过特殊的方式来强制gif图单独为一个图层(translateZ(0)或者translate3d(0,0,0)CSS3的动画也是一样(好在绝大部分情况浏览器自己会为CSS3动画的节点创建图层);

所以:将频繁重绘回流的DOM元素作为一个独立图层,那么这个DOM元素的重绘和回流只会该图层;原则上是要尽量避免新建图层的,因为这会导致图层重组(Composive Layers)时候的计算量增大。所以,只有当某些DOM元素频繁重绘回流时,才新建一个独立图层放置它们;

只会触发重绘的属性

//部分属性
color
border-style
border-radius
visibility
text-decoration
background
background-image
background-position
background-repeat
background-size
outline-color
outline
outline-style
outline-width
box-shadow

4.回流(Reflow)

render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow);

触发页面重布局(回流)的属性

频繁触发重绘和回流,会导致< UI频繁渲染。在渲染的过程中由于阻塞了js线程的执行,最终导致js执行变慢。

5.触发回流的常见操作

注:display:none 会触发 Reflow,而visibility:hidden 只会触发 Repaint,因为没有发生位置变化;

6.示例

案例一:淘宝轮播图

可以使用Chrome浏览器调试工具的Performance来观察淘宝首页一个轮播图引起的重绘回流过程:

Update Layer Tree回流和重布局:

Paint重绘:

Composite Layers图层重组:

案例二:播放器

通过Chrome调试工具的Layers选项查看图层,及新增图层的原因:

视频播放的过程中,video标签的DOM元素会一直重绘,所以把它限制在一个图层上是非常好的,这样只会涉及到这个图层的重绘,而不会影响其他图层的元素。

图层不能滥用,否则会在图层重组的过程中严重消耗性能!

比如可以将淘宝首页的所有的DOM元素都变为一个图层:在html标签中的全局样式(*)中添加transform:translateZ(0)来触发新建图层:

还可以通过添加:will-change: transform属性新建图层;

再次查看此时的图层情况,可以看到此时首页的图层非常之多,十分地卡:

7.实战优化点

如果我们需要使得动画或其他节点渲染的性能提高,需要做的就是减少浏览器在运行时所需要做的下列工作:

1、使用translate替代top等属性来改变位置;

<!DOCTYPE html>
<htmllang="en">
<head>
  <metacharset="UTF-8">
  <metaname="viewport"content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #box{
    /*方法1*/
      position: relative;
      top: 0;

     /*方法2*/
     /* transform: translateY(0); */

      width: 200px;
      height: 200px;
      background-color: pink;
    }
  </style>
</head>
<body>
  <divid="box"></div>
  <script>
    setTimeout(() => {
      document.getElementById("box").style.top = '100px'
      //document.getElementById("box").style.transform = 'translateY(100px)'
    }, 2000);
  </script>
</body>
</html>

使用top属性改变正方形位置时,存在重绘和回流Layout

而使用translate属性改变正方形位置时,并不会引起重绘和回流:

比如有的网站会有一些左右飘动的浮窗,由于这些浮窗是采用定时器来实现的,如果每经过100ms就改变浮窗的位置。这种时候使用transform来替代top/left的话1s内就减少了十次回流,十分有利于网页速度的提升。

2、使用opacity替代visibility

3、将多次改变DOM元素样式属性的操作合并成一次操作:

4、把DOM离线后再修改:

5、不要把获取某些DOM节点的属性值放在一个循环里当成循环的变量

当向浏览器请求某些 style信息的时候,浏览器就会清空(flush)队列,比如:

浏览器为了获取最精确的值,需要刷新内部队列。因为队列中可能存在影响到这些值的操作,即使没有,浏览器也会强行刷新渲染队列。这样就无法利用渲染队列的缓存来避免回流过于频繁了,所以在使用到<span style="font-size: 14px;">DOM元素这些相关的属性时,可以将获取到的属性值存在一个变量中,而不是每次都去重新获取。

6、不要使用table布局:

7、启用GPU硬件加速:

原理为:浏览器会检测一些特定的css属性,当DOM元素拥有这些css属性的时候,浏览器就会对该DOM元素启动GPU硬件加速;比如:transform: translateZ(0)transform: translate3d(0, 0, 0)这两个属性都可以启动硬件加速;硬件加速同样不能滥用,否则会导致图层过多,导致合并图层时消耗大量性能。

8、动画实现速度的选择:

9、为动画元素新建图层,提高动画元素的z-index

10、利用文档碎片(documentFragment)------vue使用了该种方式提升性能

如果我们要在一个ul中添加10000li,如果不使用文档碎片,那么我们就需要使用append进行10000次的追加,这会导致页面不停地回流,非常地消耗资源:

var oUl = document.createElement("ul"); 
for(var i=0;i<10000;i++)
{ 
    var oLi = document.createElement("li"); 
    oUl.appendChild(oLi); 
} 
document.body.appendChild(oUl);

我们可以引入createDocumentFragment()方法,它的作用是创建一个文档碎片。先将要插入10000li添加到文档碎片里,然后再一次性添加到document中。即文档碎片相当于一个临时仓库,这样能够大量减少DOM操作:

//先创建文档碎片
var oFragment = document.createDocumentFragment(); 

//再创建ul标签
var oUl = document.createElement("ul"); 
for(var i=0;i<10000;i++)
{ 
  //创建li标签
  var oLi = document.createElement("li"); 
  //先附加在文档碎片中
  oFragment.appendChild(oLi);  
}
//将文档碎片添加到ul标签中 
oUl.appendChild(oFragment);
//将ul标签添加到body标签中
document.body.appendChild(oUl);

11、如果涉及到一些可以使用合成线程来处理 CSS 特效或者动画的情况,就尽量使用 will-change 来提前告诉渲染引擎,让它为该元素准备独立的层。

12、采用虚拟DOM

13、使用requestAnimationFrame制作动画:详细内容如下。

8.请求动画帧(requestAnimationFrame

window.requestAnimationFrame() :该方法会告诉浏览器在重绘之前调用指定的函数:

window.cancelAnimationFrame(requestID):该方法取消一个先前通过调用window.requestAnimationFrame()方法添加到计划中的动画帧请求。requestID是先前调用window.requestAnimationFrame()方法时返回的ID

用途

示例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #box{
      height: 200px;
      width: 200px;
      background-color: pink;
    }
  </style>
</head>
<body>
  <div id="box"></div>
  <script>
    let i = 0
    //获取请求ID
    let id = requestAnimationFrame(move)

    function move(){
      i++
      document.getElementById('box').style.transform = `translateX(${i}px)`
      //递归调用requestAnimationFrame,更新请求ID,实现动画效果
      id = requestAnimationFrame(move)
    }

    setTimeout(() => {
      //2s后停止动画
      cancelAnimationFrame(id)
    }, 2000);
  </script>
</body>
</html>

六、函数防抖与节流

1.函数防抖

代码实现

function debounce(fn,delay){
     var timer = null
  //  清除上一次延时器
    return function(){
         clearTimeout(timer)
        //  重新设置一个新的延时器
        timer = setTimeout(() => {
            fn.call(this)
        }, delay);
    }
 }

使用函数防抖可以减少事件触发的次数和频率,在某些情况下可以起到优化的作用。比如:搜索框,对于核心业务非搜索的网站,一般都是等待用户完整输入内容后才发送查询请求,一次来减少服务器的压力。像百度这样的核心业务为搜索的网站,服务器性能足够强大,所以不进行函数防抖处理;

2.函数节流

代码实现

/* 
 节流函数:fn:要被节流的函数,delay:规定的时间
 */
functionthrottle(fn, delay){
    // 记录上一次函数出发的时间
    var lastTime = 0
    return function(){
        // 记录当前函数触发的时间
        var nowTime = new Date().getTime()
        // 当当前时间减去上一次执行时间大于这个指定间隔时间才让他触发这个函数
        if(nowTime - lastTime > delay){
            // 绑定this指向
            fn.call(this)
            //同步时间
            lastTime = nowTime
        }
    }
}

七、浏览器存储

1.Cookie

Cookie的生成方式

Cookie的缺陷

因此要慎用Cookie,不要在Cookie中存储重要和敏感的数据。

Cookie性能优化的方法

将存放静态资源的CDN服务器域名与主站的域名独立开来。这样每次请求静态文件的时候就不需要携带Cookie,从而可以节省很多流量。

举例

比如在百度进行登录的时候,请求头里面就会有Set-Cookie字段,其中的BDUSS就是标识用户登录状态的字符串:

Set-Cookie中的httponly属性表示的是禁止js脚本访问cookie,这样能够一定程度防范XSS攻击;

Chrome调试工具的Application选项中查看Cookies信息,可以发现该Cookie已经被网站“种”到Domain:.baidu.com这个域名下了,并且该Cookie也设置了HttpOnly属性:

此后浏览器的每次请求都会在请求头Request Headers中携带这一Cookie信息。刷新页面后可以看到,请求头中携带了Cookie信息BDUSS

这样服务器就知道这是已经登录的用户了。

但是不是所有的请求都需要携带Cookie信息,比如优酷:

可以看到请求index.css文件时也携带了Cookie,但是这是不必要的,这就会导致流量的浪费。

解决方法就是上面所说的:将CDN域名和主域名独立出来;

百度就是这样解决的:

可以看到请求这个静态资源的url并不是.baidu.com,而是静态资源服务器CDN;并且该请求的请求头中不会携带Cookie信息:

设置和获取Cookie

设置Cookie的方式很简单,keyvalue值通过等号连接:

document.cookie = "userName=zhangsan"

打开Application选项查看当前Cookie,可以看到Cookie已被改变:

获取Cookie

document.cookie

备注:

1. 静态资源是不会携带Cookie的;

2. Cookie一般都是后台种的,很少让前端来直接写;

3. Cookie分:持久级别、session级别;

4. Cookie一般用于存放session ID与服务器端进行通信;

2.Web Storage

LocalStorage

LocalStorageHTML5设计出来专门用于存储浏览器信息的:

举例

比如通过Chrome调试工具的Application选项可以查看淘宝中LocalStorage存储的数据:

这些数据只要不手动清除,即使关闭页面也都会存在。当需要使用图片、js/css文件等资源时就不用重新向服务器发出请求,而是可以直接使用LocalStorage中的缓存,这就是LocalStorage缓存的优势;

而Cookie就不一样了,里面存储的数据都是要带到服务器端的,例如用户登录状态,统计信息等数据:

设置和获取LocalStorage

LocalStorage提供了相对简单的API,采用的也是keyvalue的形式。

设置时通过:

localStorage.setItem("key", "value")

查看LocalStorage,同样设置成功了:

获取时通过:

localStorage.getItem("key")

其他方法

//该方法接受一个键名作为参数,并把该键名从存储中删除。
localStorage.removeItem('key');

//调用该方法会清空存储中的所有键名
localStorage.clear();

SessionStorage

SessionStorage用于存储浏览器的会话信息,标签页关闭之后它存储的数据就会被清空,而LocalStorage的数据不会被清空,这是二者的区别:

设置和获取SessionStorage

设置SessionStorage的方法与设置LocalStorage的方法类似:

//设置
sessionStorage.setItem("key", "value")

//获取
sessionStorage.getItem("key")

通过Application选项查看SessionStorage,可见已成功修改:

其他方法

//该方法接受一个键名作为参数,并把该键名从存储中删除。
sessionStorage.removeItem('key');

//调用该方法会清空存储中的所有键名
sessionStorage.clear();

3.IndexedDB

IndexedDB是浏览器提供的一种API,用于存储客户端中大量的结构化数据。该API使用索引来实现对数据的高性能搜索。虽然WebStorage对于存储较少量的数据时很有用(采用key/value的方式),但对于存储更大量的结构化数据来说,还是IndexedDB表现更加优异。

IndexedDB的应用

可以在浏览器中打印indexedDB对象:

4.PWA

PWAProgressive Web Apps)是一种Web App新模型(标准),并不是具体指某一种前沿的技术或者某一个单一的知识点。从英文缩写就能看出,这是一个渐进式的Web App,是通过一系列新的Web特性,配合优秀的UI交互设计,逐步增强用户的体验;

PWA的要求

5.Service Worker

Service Worker是一个脚本,可以使浏览器独立于当前网页,在后台运行。为实现一些不依赖页面或者用户交互的特性打开了一扇大门。在未来这些特性将包括推送信息,背景后台同步,geofencing(地理围栏定位)等它将推出的第一个首要特性,就是拦截和处理网络请求的能力,包括以编程方式来管理被缓存的响应。

Service Worker可以帮助浏览器执行大规模的运算而不阻碍主线程的执行。

Service Worker的应用

Service Worker应用过程

示例

通过Chrome调试工具的Application选项可以查看淘宝的Service Workers信息:

当我们刷新淘宝网页的时候,查看Network选项,可以从请求文件的size栏发现大量的文件都是从Service Worker缓存中请求回来的:

这样的话就可以利用Service Worker的缓存进行网站的性能优化。

以下列淘宝请求同一js文件为例,从Service Worker中加载使用了7ms

使用Ctrl + F5强制刷新后,向服务器请求同一文件花了100ms

这就是使用Service Worker性能上带来的优势。由于是从本地缓存中读取的资源,所以资源读取的速度和整体的性能都会有一个明显的提升。

八、HTTP通用缓存策略

1.缓存的简介

2.缓存相关的header字段

可以通过Chrome浏览器调试工具中的Network选项查看浏览器请求资源的情况:

注意不要勾选图中方框内的选项,否则有些请求会被过滤;

Cache-Control字段

服务器可通过httpheader中的Cache-Control字段控制客户端与服务器端之间的缓存策略,它的属性值有:

max-age

该字段指定了缓存的最大有效时间,以下为淘宝的一张图片:

max-age属性指定的时间未到期前,客户端不会向服务器发起请求,而是从缓存中直接读取该图片。上图中可以看到浏览器直接从ServiceWorker的缓存中读取了该图片资源。

Expires字段同样可以指定缓存的有效期,不过这是HTTP1.0中的字段,优先级比HTTP1.1中的Cache-Control字段的max-age属性低;

s-maxage

缓存设备总体来说有两种:浏览器(客户端)和CDN服务器;

s-maxage的优先级在Expiresmax-age三者之中是最高的,用于指定public类型缓存设备(比如CDN)上资源的有效期。如下图所示,该资源设定了该字段后,浏览器既不会使用浏览器缓存,也不会向服务器请求资源,而是向public类型的缓存设备(如CDN服务器)请求资源:

private

服务器端可以通过该属性指定某一资源只能被浏览器(客户端)缓存,而不能被代理缓存服务器(CDN)缓存。

public

服务器端可以通过该属性指定某一资源,既可以被浏览器缓存,也可以被代理缓存服务器缓存;

no-cache

no-cache属性规定了浏览器要先向服务器端发送请求确认缓存资源的新鲜度,才能决定是否使用缓存;如下图所示:

no-store

该属性指定了浏览器无论缓存资源是否过期直接跳过缓存,重新向服务器请求资源。no-store属性用的比较少。

Expires字段

这是http1.0的规范;它的值为一个绝对时间的GMT(格林威治标准时间)格式时间字符串,如Mon, 10 Jun 2015 21:31:12 GMT

该字段指定了浏览器缓存资源的过期时间,在指定的时间到期前,浏览器可以直接从本地缓存中读取数据,而无需再次向服务器发起请求,属于强缓存;相比于max-ages-maxage优先级最低,在这两个属性存在的情况下Expires字段会失效;

标识资源变化的字段

Last-Modified/If-Modified-Since

二者是基于客户端和服务端协商的缓存机制,标识资源最后更新时间的字段。last-modified字段位于response header中,If-Modified-Since字段位于request header中,二者配合着Cache-Control字段使用。

当服务器上的资源发生改变时会同步更新last-modified的字段值,当Expires字段或max-age属性指定的时间到期后,客户端会在请求头中携带If-Modified-Since字段,与服务器端资源的last-modified字段值进行比较:

举例

下图表示状态码为304的响应:

二者相等,说明资源没有发生变化,所以服务器返回状态码304,属于协商缓存,浏览器继续使用本地缓存;

If-Modified-Since字段的值就是服务器端上一次响应资源中的Last-Modified字段值;

Last-Modified的缺点

所以有了

Etag/If-None-Match

Etag字段是HTTP1.1中的标准,是一个唯一标识服务器端资源的hash值,该字段存在于响应头(reponse header)中;与请求头(request header)中的If-None-Match字段及Cache-Control字段配合使用。

只要服务器端的资源发生变化Etag值就会改变,相比于Last-Modified字段优先级更高且更有效;当Expires值或者Cache-Control字段中的max-age值到期时,客户端会在请求头中携带If-None-Match字段,该字段值为服务器端上一次响应资源中的Etag值,并与服务器端上最新资源的Etag值进行比较:

举例

下图表示状态码为304的响应:

二者相等,说明资源没有发生变化,所以服务器返回状态码304,属于协商缓存,浏览器继续使用本地缓存;

总结:

利用Etag能够更加准确的控制缓存,因为Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符;

Last-ModifiedETag是可以一起使用的,由于Etag的优先度更高,所以服务器会优先比较EtagIf-None-Match。一致的情况下,才会继续比对Last-ModifiedIf-Modified-Since,最后才决定是否返回状态码304

3.缓存策略

缓存分类

强缓存与协商缓存的区别

分级缓存策略

最下层的200状态

可以看到,从memory cache中读取缓存不需要时间,从disk cache中读取缓存则需要一定时间。

相对时间与绝对时间与服务器的设置有关,当服务器设置Atime(最后访问时间)时,二者相等;当服务器设置Mtime(绝对修改时间)时,Expires从资源的创建开始计算过期时间,Max-age从请求发起的时间开始计算过期时间;

下图便是淘宝中采用强缓存的例子,状态码为200,图片资源都是从浏览器缓存memory cache中读取,所以请求时间为0ms:

中间的304状态

下图便为协商缓存的情况,状态码为304。也可以这样理解:只要状态码是304都属于协商缓存:

最上层的200状态

如下图所示:

用户行为对缓存的影响

缓存策略过程分析

如图所示,该流程图表示服务器端在处理资源时采用缓存策略的过程:

九、服务端性能优化

1.CDN服务器

定义

网站通常将其所有的服务器都放在同一个地方,当用户群增加时,公司就必须在多个地理位置不同的服务器上部署内容。为了缩短http请求的时间,我们应该把大量的静态资源放置的离用户近一点。

内容发布网络CDNContent Delivery Networks)就是其中一种方式。CDN是一组分布在多个不同地理位置或网段的web服务器,用于更加有效的向用户发布内容。

基本思路

基础架构

最简单的CDN网络由一个DNS服务器和几台缓存服务器组成:

关于DNS解析,不一定由DNS服务器响应,一般从缓存中读取。比如电脑缓存、浏览器缓存、路由器缓存、运行商缓存等。如果缓存中没有找到,才一级一级地查询:本地DNS-> 权限DNS -> 顶级DNS -> 根DNS。全球只有13台根DNS服务器。

应用场景

总结

简单点说CDN服务器相当于顺丰快递分布于全国各地的仓库,主仓库将快递运送到这些分仓库,用户可以就近取货,由此加快了速度。

除此之外CDN服务器还有许多高级功能,比如防止DDOS攻击等,这里就不展开了;

2.SSR(Server Side Rendering)

依赖现代框架如VueReact构建的网站,往往会存在一定的问题,比如Vue框架。

Vue渲染面临的问题

首屏渲染时,要先下载和解析app.js(打包过后的Vue.js)之后,才能开始渲染页面。

优化方案

通常采用服务端渲染(SSR)的方式进行优化。所谓SSR就是利用服务器端优秀的计算能力,将一部分的页面渲染任务交由服务器端进行处理。以下为服务端渲染SSR的流程图:

服务端渲染可以很好地优化首屏渲染的问题;可以根据业务需求,适当地分配客户端和服务器端的渲染部分,综合利用客户端和服务器端的计算能力,从而达到性能优化的目的。

参考资料:

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8