浏览器性能优化

浏览器性能优化

正确的资源下载/执行优先级,并减小页面加载期间浏览器的空闲时间,是提高Web应用性能的最重要手段之一。在实际Web应用中,此优化方案被证实比减小代码大小更为直接有效,此类型的优化对产品开发节奏的影响比较小,它只须要少许的代码更改和重构。

Javascript,XHR,图片预加载

让浏览器在关键页面预加载动态资源:动态加载的JavaScript、预加载的XHR-GraphQL数据请求javascript

<link rel="preload" href="index.js" as="script" type="text/javascript">

动态加载JavaScript,一般是指经过import('...')为指定客户端由路由加载的脚本。在服务端接收到请求时,能够知道这个特定的服务端入口文件将须要哪些客户端路由的动态脚本,而且在页面初始化渲染的HTML中,为这些脚本添加预加载逻辑。java

在某个页面入口文件中,必然会执行1个特定的GraphQL请求,能够预加载这个XHR请求,这个点很是重要,由于在某些场景下GraphQL请求会消耗大量时间,页面必需要等到这些数据加载好才能开始渲染。json

<link rel="preload" href="/graphql/query?id=12345" as="fetch" type="application/json">

调整预加载优先级的好处

除了更早地开始资源加载,预加载还有额外的好处:提高异步脚本加载的网络优先级,对于重要的[异步脚本]来讲,这点很是重要,由于它们的网络优先级默认是low。这意味着它们的优先级和屏幕以外的图片同样(low),而页面的XHR请求和屏幕内的图片网络优先级则比它们要高(high)。这致使页面渲染所需的重要脚本的加载可能被阻塞,或和其余请求共享带宽。api

调整预加载优先级的问题

预加载的问题:它提供的额外控制会带来额外的责任,即设置正确的资源优先级。当在低速移动网络区域、慢WIFI网络或丢包率比较高的场景中测试时,<link rel="preload" as="script">的网络请求优先级会比<script />标签的JavaScript脚本高,而<script />标签的脚本才是页面渲染首先须要的,这将增长整个页面的加载时间。数组

只预加载路由须要的异步JavaScript

经过客户端路由当前页面须要异步加载的。浏览器

  1. 预加载全部JavaScript资源。
  2. 控制JavaScript资源加载的顺序。

图片预加载

构建一个优先任务的抽象来处理异步加载的队列,这个预加载任务在初始化时优先级是idle(利用requestIdleCallback函数,window.requestIdleCallback()方法将在浏览器的空闲时段内调用的函数排队。在主事件循环上执行后台和低优先级工做,而不会影响延迟关键事件,如动画和输入响应。),因此它会到浏览器不执行任何其余的重要任务时才开始。提高优先级的方法是经过取消全部待执行的空闲任务,这样预加载任务就能当即执行。缓存

使用early flush(提早刷新)和progressive HTML(渐进式HTML)来推送数据

如何让浏览器尚未任何服务端的HTML返回就发起请求?解决方案是服务器主动向浏览器推送资源,这有点像利用http/2push特性,它具备很是好的浏览器兼容性,而且不须要为实现此特性而增长服务端基础架构的复杂性。性能优化

它的主要实现包含两点:服务器

  • HTTP分块传输编码
  • 浏览器渐进式渲染HTML

Chunked transfer encoding(分块传输编码)是HTTP/1.1协议中的一部分,从本质上来看,它容许服务端将HTTP的返回切碎成多个chunk(块),而后以分流的形式传输给浏览器。浏览器不断接收这些块,在最后一个块到达后将它们聚合在一块儿。网络

它容许服务器在完成每一个chunk时,就将此时的HTML页面的内容流式传输到浏览器,没必要等待整个HTML完成。服务器一收到请求,就能够将HTML的头部flush给浏览器(early flush),减小了处理HTML剩余内容的时间。对于浏览器来讲,浏览器在收到HTML的头部时,就开始预加载静态资源和动态数据,此时服务器还在忙于剩余HTML内容的生成。

利用[分块传输编码]在传输完成的同时讲其它数据推送到客户端。对于服务端渲染的Web应用,通常采用HTML格式返回;对于SPA,能够用JSON格式数据推送到浏览器。

建立一个JSON缓存来存储服务端返回的数据。

// 服务端将会写下全部它已经在准备的请求路径
// 这样客户端就知道等待服务端返回数据便可,不须要本身发送XHR请求
window.__data = {
    '/my/api/path': {
        // 客户端发起请求后的回调都存在waiting数组中
        waiting: []
    }
};
window.__dataLoaded = function (path, data) {
    const cacheEntry = window.__data[path];
    if (cacheEntry) {
        cacheEntry.data = data;
        for (let i = 0; i < cacheEntry.waiting.length; i++) {
            cacheEntry.waiting[i].resolve(cacheEntry.data);
        }
        cacheEntry.waiting = [];
    }
};

在把HTML刷新到浏览器后,服务端就能够本身执行API请求的查询,完成后将JSON数据以[包含script标签的HTML片断]的形式刷新到页面中。当这个HTML片断被浏览器接收并解析后,它会被数据写入JSON缓存对象中。这里有个关键技术点:浏览器会在接收到chunks的时候就当即开始渲染。因此,能够再服务端并行生成一系列API数据,并在每一个API数据准备好时,就当即将其刷新到JS块中。

当客户端JS准备好请求某个特定数据的时候,它将先检查JSON缓存对象中有没有数据,而不是发起一个XHR请求。若是JSON缓存对象中已经有数据,它将当即获得返回;若是JSON缓存对象已标记pending,它将把请求的resolve回调注册到对应的waiting数组中,请求完成后,执行对应的resolve回调。

function queryAPI(path) {
  const cacheEntry = window.__data[path];
  if (!cacheEntry) {
    // 没有缓存对象,直接发起一个普通的XHR请求
    return fetch(path);
  } else if (cacheEntry.data) {
    // 服务端已经推送好数据
    return Promise.resolve(cacheEntry.data);
  } else {
    // 服务端正在生成数据或推送中
    // 把请求成功的resolve回调放到cacheEntry.waiting队列中
    // 当接收到数据后,回调会按顺序执行
    const waiting = {};
    cacheEntry.waiting.push(waiting);
    return new Promise((resolve) => {
      waiting.resolve = resolve;
    });
  }
}

此项优化的效果很是明显:桌面端用户访问页面的渲染完成的时间减小14%,移动端用户(有更高的网络延迟)更是减小了23%。

缓存优先

如何让页面更快地获取到数据?惟一的思路就是不经由网络的请求和推送数据。能够采用缓存优先的渲染机制来实现