前端性能优化总结

1,399 阅读33分钟

性能度量标准/设定目标

Performance

  • 指标重要性取决于应用
  • 根据度量标准设定一个现实的目标

性能度量标准

FCP < FMP < DCL(DOMContentLoaded Event) < L(Onload Event)

下表是与页面加载性能相关的用户体验。

用户体验描述
在发生吗?网页浏览顺利开始了吗?服务端有响应吗?
是否有用?是否已渲染可以与用户互动的足够内容?用户是否能看到足够的内容?
是否可用?用户是否可以和页面交互,还是页面仍在忙于加载?
是否令人愉快的?交互是否流程和自然,没有卡段或闪烁?

First paint and first contentful paint

它在发生吗?

首次绘制 (FP) 和 首次内容绘制 (FCP)。 这些指标用于标记导航之后浏览器在屏幕上渲染像素的时间点。这两个指标其实指示了我们通常所说的白屏时间。

白屏时间计算存在争议,如下:

  1. 白屏时间 = FCP - FP (个人认为)

  2. 白屏时间 = FCP - performance.timing.navigationStart(路由改变,用户再按下回车的瞬间到首次内容渲染,见 @寻找海蓝 文章

  3. 不兼容performance.timing 的浏览器,如IE8,使用如下方式:

    <!--
    白屏时间 = firstPaint - pageStartTime;
    -->
    <head>
      <meta charset="UTF-8">
      <title>白屏</title>
      <script type="text/javascript">
        window.pageStartTime = Date.now();
      </script>
      <!-- 页面 CSS 资源 -->
      <script type="text/javascript">
        // 白屏时间结束点
        window.firstPaint = Date.now();
      </script>
    </head>
    

这两个指标之间的主要差别在于:

  • FP 标记浏览器渲染任何在视觉上不同于导航前屏幕内容(就是输入网址enter之前的内容,可通过Performance查看)的时间点。
  • FCP 标记的是浏览器渲染来自 DOM 第一个内容的时间点,该内容可能是文本、图像、SVG 甚至 <canvas> 元素。

在控制台查看 paint 性能:

window.performance.getEntriesByType('paint')

在代码中查看 paint 性能:

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // `entry` is a PerformanceEntry instance.
    console.log(entry.entryType, entry.startTime, entry.duration);
  }
});

// register observer for long task notifications
observer.observe({entryTypes: ["paint"]});

First meaningful paint and hero element timing

它是否有用?

FMP(首次有意义绘制) 是回答“它是否有用?”的度量标准。当前并没有制定标准,需要根据自己的页面来确定那一部分是最重要的(主角元素计时),然后度量这部分渲染出的时间作为FMP。

chrome 提供的性能分析工具 Lighthouse 可以测量出页面的 FMP。其使用的算法是:页面绘制布局变化最大的那次绘制(根据 页面高度/屏幕高度 调节权重)

First meaningful paint = Paint that follows biggest layout change

Time to interactive

它是否可用?

可交互时间 (TTI) 指标用于标记应用已进行视觉渲染并能可靠响应用户输入的时间点。

无法响应用户输入原因:

  • 页面组件运行所需的 JavaScript 尚未加载
  • 耗时较长的任务阻塞主线程

TTI 指标可识别页面初始 JavaScript 已加载且主线程处于空闲状态(没有耗时较长的任务)的时间点。

Long tasks

它是否令人愉快的?

当用户有输入时,触发相应的事件,浏览器将相应的任务放入事件循环队列中。js 单线程逐个处理事件循环队列中的任务。 如果有一个任务需要消耗特别长的时间,那么队列中的其他任务将被阻塞,那么 ui 渲染就被阻塞了。任务耗时较长表现为滞后或卡顿,而这也是目前网页不良体验的主要根源。

  • UI渲染
  • 用户操作回调

Long Tasks API 可以将任何耗时超过 50 毫秒的任务标示为可能存在问题,并向应用开发者显示这些任务。 选择 50 毫秒的时间是为了让应用满足在 100 毫秒内响应用户输入的 RAIL 指导原则

其他

  1. 输入响应(Input responsiveness) 界面响应用户输入所需的时间

  2. 感知速度指数(Perceptual Speed Index,简称PSI)

    Speed Indx

    测量页面在加载过程中视觉上的变化速度,分数越低越好。

  3. 自定义指标

    由业务需求和用户体验来决定。

性能测试

性能测量代码最重要的规则是不应降低性能。

本地开发时性能的测量

LighthouseWeb Page Test ,这些工具不能在用户的机器上运行,所以它们不能反映用户真实的用户体验。

实际用户的设备上测量

  1. hack 的方式,可能使性能变差
  2. 新的 API
  1. 检测耗时较长任务的**hack**手段

    (function detectLongFrame() {
      var lastFrameTime = Date.now();
      requestAnimationFrame(function() {
        var currentFrameTime = Date.now();
    
        if (currentFrameTime - lastFrameTime > 50) {
          // Report long frame here...
        }
        detectLongFrame(currentFrameTime);
      });
    }());
    

此代码以无限循环的 requestAnimationFrame 开头,并记录每次迭代所花费的时间。 如果当前时间距离前次时间超过 50 毫秒,则会认为原因在于存在耗时较长的任务。 虽然大部分情况下此代码都行得通,但其也有不少缺点:

  • 此代码会给每个帧增加开销。
    • 此代码会阻止空闲块。
    • 此代码会严重消耗电池续航时间。
  1. FP 和 FCP

    [如上所诉](#1.1.1 、First paint and first contentful paint (它在发生吗?))

  2. 用关键元素测量 FMP

    标准中并未定义 FMP,需要根据页面的实际情况来定 FMP。一个较好的方式是测量页面关键元素渲染的时间。参考文章 User Timing and Custom Metrics

    测量 css 加载完成时间:

    <link rel="stylesheet" href="/sheet1.css">
    <link rel="stylesheet" href="/sheet4.css">
    <script>
    	performance.mark("stylesheets done blocking");
    </script>
    

    测量关键图片加载完成时间:

    <img src="hero.jpg" onload="performance.clearMarks('img displayed'); performance.mark('img displayed');">
    <script>
      performance.clearMarks("img displayed");
      performance.mark("img displayed");
    </script>
    

    测量文字类元素加载完成时间:

    <p>This is the call to action text element.</p>
    <script>
      performance.mark("text displayed");
    </script>
    

    计算加载时间:

    function measurePerf() {
      var perfEntries = performance.getEntriesByType("mark");
      for (var i = 0; i < perfEntries.length; i++) {
        console.log("Name: " + perfEntries[i].name +
          " Entry Type: " + perfEntries[i].entryType +
          " Start Time: " + perfEntries[i].startTime +
          " Duration: "   + perfEntries[i].duration  + "\n");
      }
    }
    
  3. 测量 TTI

    采用谷歌提供的 tti-polyfill

    import ttiPolyfill from './path/to/tti-polyfill.js';
    
    ttiPolyfill.getFirstConsistentlyInteractive().then((tti) => {
      ga('send', 'event', {
        eventCategory: 'Performance Metrics',
        eventAction: 'TTI',
        eventValue: tti,
        nonInteraction: true,
      });
    });
    

    TTI 标准定义文档

  4. 测量 Long Tasks

    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        ga('send', 'event', {
          eventCategory: 'Performance Metrics',
          eventAction: 'longtask',
          eventValue: Math.round(entry.startTime + entry.duration),
          eventLabel: JSON.stringify(entry.attribution),
        });
      }
    });
    
    observer.observe({entryTypes: ['longtask']});
    
  5. 跟踪输入延迟

    阻塞主线程的耗时较长任务可能会导致事件侦听器无法及时执行。 RAIL 性能模型指出,为提供流畅的界面体验,界面应在用户执行输入后的 100 毫秒内作出响应,若非如此,请务必探查原因。

    将事件时间戳与当前时间作比较,如果两者相差超过 100 毫秒,您可以并应该进行报告。

    const subscribeBtn = document.querySelector('#subscribe');
    
    subscribeBtn.addEventListener('click', (event) => {
      // Event listener logic goes here...
    
      // 当前时间 - 事件时间戳
      const lag = performance.now() - event.timeStamp;
      if (lag > 100) {
        ga('send', 'event', {
          eventCategory:'Performance Metric'
          eventAction: 'input-latency',
          eventLabel: '#subscribe:click',
          eventValue:Math.round(lag),
          nonInteraction: true,
        });
      }
    });
    
  6. 资源下载时长

    // 如上
    observer.observe({entryTypes: ['resource']});
    

设定目标

使用 RAIL 模型评估性能

以用户为中心;最终目标不是让您的网站在任何特定设备上都能运行很快,而是使用户满意。

  • 立即响应用户;用户注意到滞后之前您有 100 毫秒的时间可以响应用户输入。

  • 需要超过 500 毫秒才能完成的操作,请始终提供反馈。

  • 设置动画或滚动时,在 10 毫秒以内生成帧。

    浏览器需要花费时间将新帧绘制到屏幕上,只有 10 毫秒来执行代码

  • 最大程度增加主线程的空闲时间。

    推迟的工作应分成每个耗时约 50 毫秒的多个块。如果用户开始交互,优先级最高的事项是响应用户。

    要实现小于 100 毫秒的响应,应用必须在每 50 毫秒内将控制返回给主线程,这样应用就可以执行其像素管道、对用户输入作出反应,等等。

    以 50 毫秒块工作既可以完成任务,又能确保即时的响应。

  • 持续吸引用户;在 1000 毫秒以内呈现交互内容。

    在 1 秒钟内加载您的网站。否则,用户的注意力会分散,他们处理任务的感觉会中断。

    侧重于优化关键渲染路径以取消阻止渲染。

    您无需在 1 秒内加载所有内容以产生完整加载的感觉。启用渐进式渲染和在后台执行一些工作。将非必需的加载推迟到空闲时间段

  • 速度指标(Speed Index)小于1250ms;

  • 3G网络环境下可交互时间小于5s;

  • 重要文件的大小预算小于170kb;

用户如何评价性能延迟:

延迟与用户反应
0 - 16 ms人们特别擅长跟踪运动,如果动画不流畅,他们就会对运动心生反感。 用户可以感知每秒渲染 60 帧的平滑动画转场。也就是每帧 16 毫秒(包括浏览器将新帧绘制到屏幕上所需的时间),留给应用大约 10 毫秒的时间来生成一帧。
0 - 100 ms在此时间窗口内响应用户操作,他们会觉得可以立即获得结果。时间再长,操作与反应之间的连接就会中断。
100 - 300 ms用户会遇到轻微可觉察的延迟。
300 - 1000 ms在此窗口内,延迟感觉像是任务自然和持续发展的一部分。对于网络上的大多数用户,加载页面或更改视图代表着一个任务。
1000+ ms超过 1 秒,用户的注意力将离开他们正在执行的任务。
10,000+ ms用户感到失望,可能会放弃任务;之后他们或许不会再回来。

编码优化

JS

数据存取

推荐的做法是缓存对象成员值。将对象成员值缓存到局部变量中会加快访问速度

  • 字面量与局部变量的访问速度最快,数组元素和对象成员相对较慢
  • 变量从局部作用域到全局作用域的搜索过程越长速度越慢
  • 对象在原型链中存在的位置越深,找到它的速度就越慢
  • 对象嵌套的越深,读取速度就越慢

DOM编程

DOM和JavaScript是2个独立的功能,只通过API连接,用JavaScript操作DOM天生就慢,所以应尽量减少用JavaScript操作DOM。

  1. 减少访问DOM的次数,把运算尽量留在ECMAScript这一端处

  2. innerHTML在绝大多数浏览器中比原生DOM方法要快(最新版的chrome除外)。

  3. 用element.cloneNode()代替document.createElement(),稍快一些。

  4. 缓存HTML集合的length.

    js中DOM集合的"动态"特性

    //这会是一个死循环,因为取HTML集合的length会重复执行查询的过程。
    var addDivs=document.getElementsByTagName('div');
    for(var i=0,len=addDivs.length;i<len;i++){      document.body.appendChild(document.createElement('div'));
    }
    
  5. 缓存已经访问过的元素;

  6. 使用 DocumentFragment 暂存 DOM,整理好以后再插入 DOM 树;

  7. 操作 className,而不是多次读写 style

  8. 避免使用 JavaScript 修复布局。

2.1.3 使用高效的事件处理

  • 减少绑定事件监听的节点,如通过事件委托;
  • 尽早处理事件,在 DOMContentLoaded 即可进行,不用等到 load 以后。

对于 resizescroll 等触发频率极高的事件,应该通过 debounce 等机制降低处理程序执行频率。

流程控制

如何从性能方面选择for,map和forEach?

  • 避免使用for...in(它能枚举到原型,所以很慢)
  • 在JS中倒序循环会略微提升性能
  • 减少迭代的次数(减少循环次数)
  • 基于循环的迭代比基于函数(map等)的迭代快
  • look-up表代替if-else(用Map表代替大量的if-else和switch会提升性能)
    • 查找表,利用数组和对象
    • switch优于if

内存泄漏相关

  • 全局变量
  • 计时器或回调函数
  • 脱离document 的DOM 的引用
  • 闭包

其他

  • 尽量不用with,eval语句,try-catch的catch子句要谨慎使用

  • 消除闭包

  • 获取某些属性也可能造成重排

    本地——春招面试/深信服/记录.md——2. 获取某些属性也可能造成重排

    现代的浏览器都是很聪明的,由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列。但是!当你获取布局信息的操作的时候,会强制队列刷新,比如当你访问以下属性或者使用以下方法:

    • offsetTop、offsetLeft、offsetWidth、offsetHeight
    • scrollTop、scrollLeft、scrollWidth、scrollHeight
    • clientTop、clientLeft、clientWidth、clientHeight
    • getComputedStyle()
    • getBoundingClientRect
  • 不要一条一条地修改 DOM 的样式。

    • 预先定义好 class 对应的 css
    • 使用 documentFragment 对象,在内存里操作 DOM
    • 先把 DOM 给 display:none (有一次 repaint),再把他显示出来
    • clone DOM 节点到内存,再交换
  • 不要把 DOM 节点的属性值放在一个循环里当成循环里的变量。不然这会导致大量地读写这个结点的属性。

CSS

css性能

CSS性能优化的8个技巧

css 选择器

  • 减少css嵌套,最好不要套三层以上

  • 不要在ID选择器前面进行嵌套

  • 减少通配符*或者类似[hidden="true"]这类选择器的使用

  • 不要在类名前面加上标签名

    别使用p.ty_p 来进行定位,这样往往效率更差,类名应该在全局范围除非公用否则是唯一的。

css 所占大小、公用

  • 缩写css

  • 去除无用CSS

  • 拆分出公共css文件,建立公共样式类

  • cssSprite,合成所有icon图片

    用宽高加上bacgroud-position的背景图方式显现出我们要的icon图,这是一种十分实用的技巧,极大减少了http请求。

利用原有机制

  • 巧妙运用css的继承机制
  • 少用css rest

css 编码

  • 尽可能的修改层级比较低的 DOM节点。减少 reflow 面积

  • 千万不要使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。

  • 使用 fixedabsoluteposition,修改他们的 CSS 是会大大减小 reflow

  • transform,硬件加速

  • 减少使用昂贵的属性

    所有需要浏览器进行操作或计算的属性相对而言都需要花费更大的代价,我们应该尽量减少使用昂贵属性,如box-shadow/border-radius/filter/opacity/:nth-child等。

  • <link> 代替 @import

  • 不用css表达式

文件加载

见下文 [4.3 优先加载关键的CSS](#4.3 优先加载关键的CSS)

css动画优化

CSS3 动画卡顿性能优化的完美解决方案

概念

  1. 主线程负责:运行 JavaScript;计算 HTML 元素的 CSS 样式;页面的布局;将元素绘制到一个或多个位图中;将这些位图交给合成线程。
  2. 合成线程负责:通过 GPU 将位图绘制到屏幕上;通知主线程更新页面中可见或即将变成可见的部分的位图;计算出页面中哪部分是可见的;计算出当你在滚动页面时哪部分是即将变成可见的;当你滚动页面时将相应位置的元素移动到可视区域。

优化方案

  • 开启CSS3硬件加速transform

  • 尽量使用 transform 当成动画熟悉,避免使用 height,width,margin,padding 等

  • will-change

    告知浏览器该元素会有哪些变化的方法,这样浏览器可以在元素属性真正发生变化之前提前做好对应的优化准备工作。

  • 动画过程有闪烁(通常发生在动画开始的时候),可以尝试下面的Hack

    -webkit-backface-visibility: hidden;
    -moz-backface-visibility: hidden;
    -ms-backface-visibility: hidden;
    backface-visibility: hidden;
    -webkit-perspective: 1000;
    -moz-perspective: 1000;
    -ms-perspective: 1000;
    perspective: 1000;
    

例子

例如tranform:translate(-20px,0)到transform:translate(0,0),主线程只需要进行一次tranform:translate(-20px,0)到transform:translate(0,0),然后合成线程去一次将-20px转换到0px,这样的话,总计1+20计算

在使用height,width,margin,padding作为transition的值时,会造成浏览器主线程的工作量较重,例如从margin-left:-20px渲染到margin-left:0,主线程需要计算样式margin-left:-19px,margin-left:-18px,一直到margin-left:0,而且每一次主线程计算样式后,合成进程都需要绘制到GPU然后再渲染到屏幕上,前后总共进行20次主线程渲染20次合成线程渲染,20+20次,总计40次计算。

HTML

可参考 SEO优化 相关,见 本地——/SEO优化/记录.md

  1. title 标签

    帮助用户和搜索引擎判断当前页面的主旨和中心思想,将当前页面最核心的关键词写进title标签中

  2. mate 标签的 keywordsdescription

  3. h1-h6标签

    h标签是搜索引擎在排名时重点考虑的一个因素,一级标题具备更多的权重,在同一个页面中只能出现一次,而其它标题则可以出现多次,根据需要表现的内容的重要程度,分别使用不同的标题标签

  4. 网站结构布局:尽量简单、开门见山,提倡扁平化结构

  5. 语义化书写HTML代码

  6. <a> title ,<img>alt 属性加以说明

  7. 避免空的 src 和 href

    遇到空字符串作为URI时,它被视为相对URI。

    浏览器向您的服务器发出另一个请求。

iframe

使用 iframe 可以在页面中嵌入 HTML 文档,但有利有弊。

优点:

  • 可以用来加载速度较慢的第三方资源,如广告、徽章;
  • 可用作安全沙箱
  • 可以并行下载脚本。

缺点:

  • 加载代价昂贵,即使是空的页面;

  • 阻塞页面 load 事件触发;

    Iframe 完全加载以后,父页面才会触发 load 事件。 Safari、Chrome 中通过 JavaScript 动态设置 iframe src 可以避免这个问题。

  • 缺乏语义。

静态资源优化

使用Brotli或Zopfli进行纯文本压缩

brotli压缩

在最高级别的压缩下Brotli会非常慢(但较慢的压缩最终会得到更高的压缩率)以至于服务器在等待动态资源压缩的时间会抵消掉高压缩率带来的好处,但它非常适合静态文件压缩,因为它的解压速度很快。

使用Zopfli压缩可以比Zlib的最大压缩提升3%至8%。

3图片优化

web前端优化之图片优化

浅探前端图片优化

3.2.1 响应式图片

img元素srcset属性浅析

不同的尺寸屏幕加载不同尺寸的图片,节省网站的访问流量和页面渲染的效率

  1. 可以通过服务器图片资源配置命名规则来获取图片

    <img src="bgimg-320.jpg" /><img src="bgimg-480.jpg" />
    
  2. 通过css定义来加载不同的背景bg图片

    /* 第一种 */
    @media only screen and (max-width : 480px) {
      .img {background-image: url(bg-480.jpg);}
    }
    @media only screen and (max-width : 360px) {
      .img {background-image: url(bg-360.jpg);}
    }
    
    /* 第二种 */
    /* 
    为普通屏幕使用 pic-1.jpg,为高分屏使用 pic-2.jpg,如果更高的分辨率则使用 pic-3.jpg
    */
    body {
      background-image: -webkit-image-set(
        url(../images/pic-1.jpg) 1x, 
        url(../images/pic-2.jpg) 2x, 
        url(../images/pic-3.jpg) 600dpi);
    	background-image: image-set( 
        url(../images/pic-1.jpg) 1x, 
        url(../images/pic-2.jpg) 2x, 
        url(../images/pic-3.jpg) 600dpi);
     }
    
  3. Img的srcsetsizes的方法

    这两个img属性是html5的属性,有浏览器的兼容问题

    <img class="img"  src="imgbg-320.jpg" 
    	srcset="imgbg-320.jpg 320w, imgbg-360.jpg 360w, imgbg-480px.jpg 480w"
    	sizes="(max-width: 480px) 480px, 320px">
    

    src: 当设备不支持srcsetsizes属性时,使用这个属性。

    srcset: 指定图片的地址和对应的图片质量。

    sizes: 用来设置图片的尺寸零界点。

    <img sizes="[media query] [length], [media query] [length] ... "/>
    

    对于srcsetsizes详解点击查看

  4. picture 标签实现

    通过媒体查询的方式,根据页面宽度(当然也可以添加其他参考项)加载不同图片,具体picture详情点击查看

    <picture>  
      <source srcset="3.jpg" media="(min-width: 320px)">  
      <source srcset="2.jpg" media="(min-width: 480px)">  
      <img srcset="1.jpg">  
    </picture>
    

webP

WebP 相对于 PNG、JPG 有什么优势?

WebP的优势在于它具有更优的图像数据压缩算法,在拥有肉眼无法识别差异的图像质量前提下,带来更小的图片体积,同时具备了无损和有损的压缩模式、Alpha 透明以及动画的特性,在 JPEG 和 PNG 上的转化效果都非常优秀、稳定和统一。

其他

  • CSSSprites(背景精灵图/雪碧图)
  • csscss3制作简单的图标和动画(代替gif图片)
  • 字体图库代替图标, Iconfont
  • SVG技术替换图片
  • html5 canvas绘画图形
  • favicon.ico要小而且可缓存
  • 压缩
  • 图片延迟加载(懒惰加载)(js/lazyload.js)
  • base64

Gzip

前端性能优化之gzip

  • gzip能在压缩的基础上再进行压缩50%以上
  • 请求头中有个accept-Encoding来标识对压缩的支持。如果客户端支持gzip压缩,响应时对请求的资源进行压缩并返回给客户端,浏览器按照自己的方式解析,在http响应头,我们可以看到content-encoding:gzip,这是指服务端使用了gzip的压缩方式。
  • node读取的是生成目录中的文件,所以要先用webpack等其他工具进行压缩成gzip

其他

CDN

【前端词典】CDN 带来这些性能优化 —— 关键

趣讲CDN

CDN的全称是 Content Delivery Network,即内容分发网络。CDN 是构建在网络之上的内容分发网络,依靠部署在各地的服务器,通过中心平台的负载均衡内容分发调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。

CDN 的优势

  1. CDN 节点解决了跨运营商和跨地域访问的问题,访问延时大大降低;
  2. 大部分请求在 CDN 边缘节点完成,CDN 起到了分流作用,减轻了源站的负载
  3. 降低“广播风暴”的影响,提高网络访问的稳定性;节省骨干网带宽,减少带宽需求量。
  4. 跨域不携带cookie

核心内容

  1. 缓存

    将从根服务器请求来的资源按要求缓存。

  2. 回源

    当有用户访问某个资源的时候,如果被解析到的那个 CDN 节点没有缓存响应的内容,或者是缓存已经到期,就会回源站去获取。没有人访问,CDN 节点不会主动去源站请求资源。

资源加载/交付优化

对页面加载资源以及用户与网页之间的交付过程进行优化。

异步无阻塞加载JS

【JS】defer / async

JS的加载与执行会阻塞页面渲染,可以将Script标签放到页面的最底部。但是更好的做法是异步无阻塞加载JS。有多种无阻塞加载JS的方法:

  • defer
  • async
  • 动态创建script标签
  • 使用XHR异步请求JS代码并注入到页面。

优先加载关键的CSS

CSS资源的加载对浏览器渲染的影响很大,默认情况下浏览器只有在完成<head>标签中CSS的加载与解析之后才会渲染页面。

内联首屏关键CSS(Critical CSS)

初始拥塞窗口

为什么都说首屏html大小限制在14KB以内

htmlSize<(14.6kb===),TCP(数据段)的荷载<=MSS<MTUhtml Size < (14.6 kb === ), TCP包(数据段)的荷载 <= MSS < MTU
  • MSS:maximum segment size,最大分节大小,为TCP数据包每次传输的最大数据分段大小,一般由发送端向对端TCP通知对端在每个分节中能发送的最大TCP数据。MSS值为MTU值减去IPv4 Header(20 Byte)和TCP header(20 Byte)得到。

  • MTU:maximum transmission unit,最大传输单元,由硬件规定,如以太网的MTU为1500字节。

  • MSS最大为1500 - 6 - 2 - 20 - 20=1452

    • PPPoE首部6
    • PPP协议2
    • 数据链路层最大data为1500-8=1492
    • IPv4首部最少20,IPv6首部40
    • TCP首部最少20

TCP 慢启动),新的 TCP 连接无法立即使用客户端和服务器之间的全部有效带宽。因此,在通过新连接进行首次往返的过程中,服务器最多只能发送 10 个 TCP 数据包(约 14KB),然后必须等待客户端确认已收到这些数据,才能增大拥塞窗口并继续发送更多数据。

内联CSS能够使浏览器开始页面渲染的时间提前,减少首次有效绘制时间,因为在HTML下载完成之后就能渲染了。

异步加载CSS

CSS会阻塞渲染,在CSS文件请求、下载、解析完成之前,浏览器将不会渲染任何已处理的内容。

实现浏览器异步加载CSS:

  • JavaScript动态创建样式表link元素

    // 创建link标签
    const myCSS = document.createElement( "link" );
    myCSS.rel = "stylesheet";
    myCSS.href = "mystyles.css";
    // 插入到header的最后位置
    document.head.insertBefore( myCSS, document.head.childNodes[ document.head.childNodes.length - 1 ].nextSibling );
    
  • link media 属性

    将link元素的media属性设置为用户浏览器不匹配的媒体类型(或媒体查询),如media="print",甚至可以是完全不存在的类型media="noexist"。对浏览器来说,如果样式表不适用于当前媒体类型,其优先级会被放低,会在不阻塞页面渲染的情况下再进行下载。

    在文件加载完成之后,将media的值设为screenall,从而让浏览器开始解析CSS。

    <link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'">
    
  • link rel 属性

    Alternative style sheets

    通过rel属性将link元素标记为alternate备用样式表

    <link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'">
    
  • rel="preload",Web标准

    <link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'">
    

    as是必须的。忽略as属性,或者错误的as属性会使preload等同于XHR请求,浏览器不知道加载的是什么内容,因此此类资源加载优先级会非常低。

    看起来,rel="preload"的用法和上面两种没什么区别,都是通过更改某些属性,使得浏览器异步加载CSS文件但不解析,直到加载完成并将修改还原,然后开始解析

    但是使用preload,比使用不匹配的media方法能够更早地开始加载CSS

懒加载

  • 可以延迟加载图片、视频、广告脚本、或任何其他资源。
  • 可以先加载低质量或模糊的图片,当图片加载完毕后再使用完整版图片替换它。

延迟加载所有体积较大的组件、字体、JS、视频或Iframe是一个好主意

Intersection Observer

【译】使用 Intersection Observer 实现图片延迟加载

let observer = new IntersectionObserver(function(changes) {
  console.log(changes);
  changes.forEach(function(element, index) {
    // statements
    if (element.intersectionRatio > 0 && element.intersectionRatio <= 1) {
      element.target.src = element.target.dataset.src;
    }
  });
});
let listItems = document.querySelectorAll('.list-item-img');
listItems.forEach(function(item) {
  observer.observe(item);
});

scroll

function lazyload() {
  // ...
  var seeHeight = document.documentElement.clientHeight;
  var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  for(var i = n; i < len; i++) {
    if(images[i].offsetTop < seeHeight + scrollTop) {
      if(images[i].getAttribute('src') === 'images/loading.gif') {
        images[i].src = images[i].getAttribute('data-src');
      }
      n = n + 1;
    }
  }
}
}
    var loadImages = lazyload();
    loadImages();          //初始化首页的页面图片
    window.addEventListener('scroll', throttle(loadImages, 500, 1000), false);

资源提示(Resource Hints)

一箩筐的预加载技术

Resource Hints(资源提示)定义了HTML中的Link元素与dns-prefetchpreconnectprefetchprerender之间的关系。它可以帮助浏览器决定应该连接到哪些源,以及应该获取与预处理哪些资源来提升页面性能。

DNS prefetch

Chrome总是会做类似的处理,用户只要在地址栏敲入一部分域名,如果命中了历史常用的网站,Chrome就会提前解析DNS、预拉取页面。

Firefox 3.5+ 也内置了 DNS Prefetching 技术并对DNS预解析做了相应优化设置

DNS prefetching通过指定具体的URL来告知客户端未来会用到相关的资源,使浏览器尽早的解析DNS。比如我们需要一个在example.com的图片或者视频文件。在<head>就可以这么写:

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

当请求这个域名下的文件时就不需要等待DNS查询了。

常见场景:

  • 项目中有用到第三方的代码
  • 静态资源和HTML不在一个域上,而在CDN上
  • 在重定向前可以加上DNS prefetch

Preconnect

preconnect不光会解析DNS,还会建立TCP握手连接和TLS协议(如果需要)。用法如下:

<link rel="preconnect" href="http://css-tricks.com">

Prefetch

注意:prefetch并没有同域的限制

当能确定网页在未来一定会使用到某个资源时,开发者可以让浏览器提前请求并且缓存好以供后续使用。prefetch支持预拉取图片、脚本或者任何可以被浏览器缓存的资源。

<link rel="prefetch" href="image.png">

subresource

可以用来指定资源是最高优先级的。比如,在Chrome和Opera中我们可以加上下面的代码:

<link rel="subresource" href="styles.css">

Prerender

prerender可以让浏览器提前加载指定页面的所有资源。

<link rel="prerender"  href="/thenextpage.html" />

prerender就像是在后台打开了一个隐藏的tab,会下载所有的资源、创建DOM、渲染页面、执行JS等等。如果用户进入指定的链接,隐藏的这个页面就会进入马上进入用户的视线。

常见场景:

  • 搜索到了一个明显正确的结果时
  • 登录成功后的页面就很可能接下来会被加载
  • 阅读一个多页面的文章或者有页码的内容时

Preload

Preload:有什么好处?(上)

Preload规范)是一项新的Web标准,旨在提升性能,让Web开发者对加载的控制更加粒度化。它让开发者有自定义加载逻辑的能力,免受基于脚本的loader所带来的性能损耗。

as 属性可以让浏览器做到很多subresource和prefetch做不到的事情:

  • 浏览器可以设置正确的资源优先级
  • 浏览器会保证请求对应正确的内容安全策略(Content-Security-Policy )指令,不会发起非法请求。
  • 浏览器会基于资源类型发送正确的 Accept 首部。(比如获取图片时指定对“image/webp”的支持)
  • 浏览器知道资源的类型,所以可以稍后决定资源是否在后续请求中保持可重用。

快速响应的用户界面

应用越复杂,主动管理UI线程就越重要

部分指标

  1. PSI(Perceptual Speed Index,感知速度指数)

    Speed Indx

    • 骨架屏
    • Loading 动画过渡
  2. 输入响应(Input responsiveness)指标

大任务处理(同上):

  • 将一个大任务拆分成多个小任务分布在不同的Macrotask中执行(通俗的说是将大的JS任务拆分成多个小任务异步执行)。
  • 使用WebWorkers,它可以在UI线程外执行JS代码运算,不会阻塞UI线程,所以不会影响用户体验。

其他

  • 减少重定向

  • 减少DNS查找

    **DNS查找通常是blocking call,**就是说在得到结果之后才能继续,所以越多的DNS查找,反应速度就越慢。

构建优化

使用预编译

Vue单文件组件开发项目,组件会在编译阶段将模板编译为渲染函数。执行时可以直接执行渲染函数进行渲染。

而如果直接引入vue.min.js,运行时需要先将模板编译成渲染函数,再执行渲染函数进行渲染。

使用 Tree-shaking、Scope hoisting、Code-splitting

  1. Tree-shaking是一种在构建过程中清除无用代码的技术。通过在 package.json 中添加 sideEffects 来启用 Tree Shaking ,即摇树优化,帮助我们删掉一些不用的代码。

  2. Webpack 3 的新功能:Scope Hoisting

    作用:

    • 检查import链,并尽可能的将散乱的模块放到一个函数中,前提是不能造成代码冗余
    • 可以让代码体积更小
    • 可以降低代码在运行时的内存开销,同时它的运行速度更快,减少函数声明
    • 从局部作用域到全局作用域的搜索过程变短
    module.exports = {
      plugins: [
        new webpack.optimize.ModuleConcatenationPlugin()
      ]
    }
    
  3. code-splitting,把代码分离到不同的bundle中,然后可以按需加载并行加载这些文件。code-splitting可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。

服务端渲染(SSR)

单页应用需要等JS加载完毕后在前端渲染页面,也就是说在JS加载完毕并开始执行渲染操作前的这段时间里浏览器会产生白屏。

服务端渲染(Server Side Render,简称SSR)的意义在于弥补主要内容在前端渲染的成本减少白屏时间,提升首次有效绘制的速度

做法是:使用服务端渲染静态HTML来获得更快的首次有效绘制,一旦JavaScript加载完毕再将页面接管下来。

Dynamic import

在之前版本的 webpack 中,我们想实现动态加载使用的是 require.ensure ,而在新版本中,取而代之的 import() ,这是TC39关于使用 import()的提案。

使用import函数可以在运行时动态地加载ES2015模块,从而实现按需加载的需求。

单页应用中,切换路由的时候动态导入当前路由所需的模块,会避免加载冗余的模块(试想如果在首次加载页面时一次性把整个站点所需要的所有模块都同时加载下来会加载多少非必须的JS,应该尽可能的让加载的JS更小,只在首屏加载需要的JS)。

使用静态import导入初始依赖模块。其他情况下使用动态import按需加载依赖

缓存

彻底弄懂HTTP缓存机制及原理 —— 主要

深入理解浏览器的缓存机制

http协商缓存VS强缓存

深入理解浏览器的缓存机制

概述

设计一个无懈可击的浏览器缓存方案:关于思路,细节,ServiceWorker,以及HTTP/2

  • Memory CacheHTTP Cache、``Service WorkerPush Cache`
状态类型说明
200form memory cache不请求网络资源,资源在内存当中,一般脚本、字体、图片会存在内存当中
200form disk ceche不请求网络资源,在磁盘当中,一般非脚本会存在内存当中,如css等
200资源大小数值从服务器下载最新资源
304报文大小请求服务端发现资源没有更新,使用本地资源

Memory Cache

主要包含的是当前文档中页面中已经抓取到的资源。例如页面上已经下载的样式、脚本、图片等。

其中最重要的缓存资源其实是preloader相关指令(例如<link rel="prefetch">)下载的资源。

内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,而且资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-TypeCORS等其他特征做校验。

Service workers

本质上充当Web应用程序与浏览器之间的代理服务器

HTTP缓存

强制缓存

强制缓存(size: from disk cache)、对比缓存(status: 304)。

缓存规则信息包含在响应header

(1)强制缓存优先级高于对比缓存。

(2)对于强制缓存来说,响应header中会有两个字段来标明失效规则(Expires、Cache-Control)

  • Expires的值为服务端返回的到期时间,即下一次请求时,请求时间小于服务端返回的到期时间,直接使用缓存数据。不过Expires 是HTTP 1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略。另一个问题是,到期时间是由服务端生成的,但是客户端时间可能跟服务端时间有误差,这就会导致缓存命中的误差。所以HTTP 1.1 的版本,使用Cache-Control替代。
  • Cache-Control 是最重要的规则。常见的取值有private、public、no-cache、max-age,no-store,默认为private。
    • private: 客户端可以缓存,防止信息泄漏。
    • public: 客户端和代理服务器都可缓存(前端的同学,可以认为public和private是一样的)
    • max-age=xxx: 缓存的内容将在 xxx 秒后失效
    • no-cache: 需要使用对比缓存来验证缓存数据(后面介绍)
    • no-store: 所有内容都不会缓存,强制缓存,对比缓存都不会触发(对于前端开发来说,缓存越多越好,so...基本上和它说886)
  • Pragma,HTTP/1.0 中规定的通用首部。

    // 
    Pragma: no-cache
    

协商缓存

(1)对比缓存,顾名思义,需要进行比较判断是否可以使用缓存。浏览器第一次请求数据时,服务器会将缓存标识与数据一起返回给客户端,客户端将二者备份至缓存数据库中。再次请求数据时,客户端将备份的缓存标识发送给服务器,服务器根据缓存标识进行判断,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据。缓存标识的传递是我们着重需要理解的,它在请求header和响应header间进行传递,一共分为两种标识传递,接下来,我们分开介绍。

  • Last-Modified(response header) / If-Modified-Since(request header用来发送last-modified)
  • Etag(服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)) / If-None-Match(发送Etag)。(优先级高于Last-Modified / If-Modified-Since)

push 缓存

“推送缓存”是针对HTTP/2标准下的推送资源设定的。推送缓存是session级别的,如果用户的session结束则资源被释放;即使URL相同但处于不同的session中也不会发生匹配。推送缓存的存储时间较短,在Chromium浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。

缓存失效操作

用户操作Expires/Cache ControlLast-Modified/Etag
地址栏回车有效有效
新开窗口有效有效
前进/后退有效有效
F5/按钮刷新无效,重置max-age=0有效
Ctrl+F5无效,重置cache-control: no-cache无效,请求头丢弃该选项

其他

  • localStorage

  • h5 离线缓存机制-manifest

    manifest是一个同名后缀为manifest的文件,在文件中定义那些需要缓存的文件,支持manifest的浏览器,将会按照manifest文件的规则进行保存数据,从而在没有网络的情况下,也可以访问

    • 离线浏览 —— 用户可以在离线状态浏览⽹网站旧数据
    • 更快的速度 —— 因为数据存储在本地,所以速度会更快
    • 减轻服务器的负载 —— 浏览器只会下载在服务器上发生改变的资源

性能监控

深入理解前端性能监控

​ 性能检测工具来持续监视网站的性能

其他

  • HTTP2
  • 使用最高级的CDN(付费的比免费的强的多)
  • 减少 Cookie 大小
  • 静态资源使用无 Cookie 域名

参考