server side rendering/ssr csr Rehydration Prerendering

performance :
TTFB: 第一个字节的时间 – 被视为点击链接和第一个内容之间的时间。
FP: First Paint – 第一次任何像素变得对用户可见。
FCP: First Contentful Paint – 请求内容(文章正文等)变得可见的时间。
TTI: 交互时间 – 页面变为交互的时间(连接的事件等)。

static rendering

静态渲染 在构建时发生,并提供快速的First Paint,First Contentful Paint和Time To Interactive – 假设客户端JS的数量有限。

静态渲染的一个缺点是必须为每个可能的URL生成单独的HTML文件。如果您无法提前预测这些URL的内容,或者对于具有大量唯一页面的网站,这可能具有挑战性甚至是不可行的。

React用户可能熟悉GatsbyNext.js静态导出Navi – 所有这些都可以方便作者使用组件。但是,了解静态呈现和预呈现之间的区别非常重要:静态呈现页面是交互式的,无需执行太多客户端JS,而预呈现改进了必须启动的单页应用程序的First Paint或First Contentful Paint客户端,以使页面真正具有交互性。

首屏优化

组件化分治思想:

  • 将各模块拆分为组件粒度
  • 将组件依赖的资源全部封装在组件内部进行调用

加载优先级

  • 优先加载首屏可见模块
  • 其余不可见模块懒加载,待可见或即将可见时加载

如何解决判断可见性问题?

从前我们都是通过监听滚动事件、resize 事件来判断模块是否可见,代码不仅繁琐,而且一不小心没有函数去抖就又可能导致严重的性能问题。

现在我们有了更好的选择—— IntersectionObserver API ,IntersectionObserver 允许你配置一个回调函数,每当 target ,元素和设备视口或者其他指定元素发生交集的时候该回调函数将会被执行。这个 API 的设计是异步的,而且保证你的回调执行次数是非常有限的,而且回调是会在主线程空闲时才执行,在性能方面表现更优,使用起来也更简单。

如何尽可能懒的条件渲染?

在解决了加载条件的判断之后,我们需要解决加载条件为假的情况下不去渲染、加载条件为真的时候才渲染的问题,这里的答案非常简单:使用 Vue.js 提供的 v-if 指令,就可以做到真正的惰性渲染。

如果可见后进行初始渲染,可见前如何显示?

用户体验比较差,最开始是白屏,然后突然又渲染出现内容
最致命的是我们判断可见性是需要一个目标来观察的,如果什么不都渲染,我们就无从观察。

这里引入一个骨架屏的概念,我们为真实的组件做一个在尺寸、样式上非常接近真实组件的组件,叫做骨架屏。

如何提升切换时的体验?

如何提升切换时的体验?
在真实组件开始渲染的时候,需要一定的时间和空间,时间指的是真实组件从创建到渲染的时间,包括请求接口、请求资源和渲染的时间,空间指的是页面布局中需要给真实组件留出刚好的位置,避免产生抖动。

这里我们可以使用 Vue.js 内置的 transition 组件自定义骨架组件和真实组件的进入和离开效果,通过合理的布局和定位,减少切换时的抖动,https://github.com/xunleif2e/vue-lazy-component

requestAnimationFrame

浏览器如面每次要重绘,就会通知requestAnimationFrame

setTimeout的执行时间并不是确定的。在Javascript中, setTimeout 任务被放进了异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,因此 setTimeout 的实际执行时间一般要比其设定的时间晚一些。

刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同设备的屏幕刷新频率可能会不同,而 setTimeout只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同

requestAnimationFrame 它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,

例子

而setTimeout每隔10ms设置图像向左移动1px, 就会出现如下绘制过程:

  • 第0ms: 屏幕未刷新,等待中,setTimeout也未执行,等待中;
  • 第10ms: 屏幕未刷新,等待中,setTimeout开始执行并设置图像属性left=1px;
  • 第16.7ms: 屏幕开始刷新,屏幕上的图像向左移动了1px, setTimeout 未执行,继续等待中;
  • 第20ms: 屏幕未刷新,等待中,setTimeout开始执行并设置left=2px;
  • 第30ms: 屏幕未刷新,等待中,setTimeout开始执行并设置left=3px;
  • 第33.4ms:屏幕开始刷新,屏幕上的图像向左移动了3px, setTimeout未执行,继续等待中;

从上面的绘制过程中可以看出,屏幕没有更新left=2px的那一帧画面,图像直接从1px的位置跳到了3px的的位置,这就是丢帧现象,这种现象就会引起动画卡顿。

scrollTop 平滑滚动效果CSS3似乎是无能为力的,还是要JS出马,势必requestAnimationFrame就可以大放异彩


var progress = 0;

//回调函数
function render() {
  progress += 1; //修改图像的位置
  if (progress < 100) { //在动画没有结束前,递归渲染 
     window.requestAnimationFrame(render); 
  } 
} 
//第一帧渲染 
window.requestAnimationFrame(render);

!!!CPU节能:使用setTimeout实现的动画,当页面被隐藏或最小化时,setTimeout 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费CPU资源。

而requestAnimationFrame则完全不同,当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。

 !!!函数节流:在高频率事件(resize,scroll等)中,为了防止在一个刷新间隔内发生多次函数执行,使用requestAnimationFrame可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。一个刷新间隔内函数执行多次时没有意义的,因为显示器每16.7ms刷新一次,多次绘制并不会在屏幕上体现出来。

performance 前端性能监控

Performance是一个做前端性能监控离不开的API,最好在页面完全加载完成之后再使用,因为很多值必须在页面完全加载之后才能得到。最简单的办法是在window.onload事件中读取各种数据。

按触发顺序排列所有属性:(更详细标准的解释请参看:W3C Editor’s Draft)
navigationStart:在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等
unloadEventStart:前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0
unloadEventEnd:和 unloadEventStart 相对应,返回前一个网页 unload 事件绑定的回调函数执行完毕的时间戳
redirectStart:第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0
redirectEnd:最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内的重定向才算,否则值为 0
fetchStart:浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前
domainLookupStart:DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
domainLookupEnd:DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
connectStart:HTTP(TCP) 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等,如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间
connectEnd:HTTP(TCP) 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等,如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间

注意:这里握手结束,包括安全连接建立完成、SOCKS 授权通过

secureConnectionStart:HTTPS 连接开始的时间,如果不是安全连接,则值为 0
requestStart:HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存,连接错误重连时,这里显示的也是新建立连接的时间
responseStart:HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存
responseEnd:HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存
domLoading:开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件
domInteractive:完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件

!!!只是 DOM 树解析完成,这时候并没有开始加载网页内的资源

domContentLoadedEventStart:DOM 解析完成后,网页内资源加载开始的时间,文档发生 DOMContentLoaded事件的时间
domContentLoadedEventEnd:DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕),文档的DOMContentLoaded 事件的结束时间
domComplete:DOM 树解析完成,且资源也准备就绪的时间,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件
loadEventStart:load 事件发送给文档,也即 load 回调函数开始执行的时间,如果没有绑定 load 事件,值为 0
loadEventEnd:load 事件的回调函数执行完毕的时间,如果没有绑定 load 事件,值为 0

常用计算:

DNS查询耗时 :domainLookupEnd – domainLookupStart
TCP链接耗时 :connectEnd – connectStart
request请求耗时 :responseEnd – responseStart
解析dom树耗时 : domComplete – domInteractive
白屏时间 :responseStart – navigationStart
domready时间(用户可操作时间节点) :domContentLoadedEventEnd – navigationStart
onload时间(总下载时间) :loadEventEnd – navigationStart

now()

performance.now()是当前时间与performance.timing.navigationStart的时间差,以微秒(百万分之一秒)为单位的时间,与 Date.now()-performance.timing.navigationStart的区别是不受系统程序执行阻塞的影响,因此更加精准

优化 JavaScript 执行

  • 对于动画效果的实现,避免使用 setTimeout 或 setInterval,请使用 requestAnimationFrame。
  • 将长时间运行的 JavaScript 从主线程移到 Web Worker。
  • 使用微任务来执行对多个帧的 DOM 更改。
  • 使用 Chrome DevTools 的 Timeline 和 JavaScript 分析器来评估 JavaScript 的影响
  •  

使用 requestAnimationFrame 来实现视觉变化

/**
 * If run as a requestAnimationFrame callback, this
 * will be run at the start of the frame.
 */
function updateScreen(time) {
  // Make visual updates here.
}

requestAnimationFrame(updateScreen);

保证 JavaScript 在帧开始时运行的唯一方式是使用 requestAnimationFrame。

下次重绘之前执行 callback, callback 会接受一个当前执行的时间戳

某些框架或示例可能使用 setTimeout或 setInterval 来执行动画之类的视觉变化,但这种做法的问题是,回调将在帧中的某个时点运行,可能刚好在末尾,而这可能经常会使我们丢失帧,导致卡顿。

事实上,jQuery 目前的默认 animate行为是使用 setTimeout!强烈建议您打上补丁程序以使用 requestAnimationFrame

降低复杂性或使用 Web Worker

var dataSortWorker = new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);

// The main thread is now free to continue working on other things...

dataSortWorker.addEventListener('message', function(evt) {
   var sortedData = evt.data;
   // Update data on screen...
});

并非所有工作都适合此模型:Web Worker 没有 DOM 访问权限。如果您的工作必须在主线程上执行,请考虑一种批量方法,将大型任务分割为微任务,每个微任务所占时间不超过几毫秒,并且在每帧的 requestAnimationFrame 处理程序内运行。

var taskList = breakBigTaskIntoMicroTasks(monsterTaskList);
requestAnimationFrame(processTaskList);

function processTaskList(taskStartTime) {
  var taskFinishTime;

  do {
    // Assume the next task is pushed onto a stack.
    var nextTask = taskList.pop();

    // Process nextTask.
    processTask(nextTask);

    // Go again if there’s enough time to do the next task.
    taskFinishTime = window.performance.now();
// 渲染之前如果 js 执行时间小于3 可以在执行 下个 taskList 任务,如果大于3 就 放在下次渲染前执行   } while (taskFinishTime - taskStartTime < 3);   if (taskList.length > 0)     requestAnimationFrame(processTaskList); //递归 执行下次任务 }

此方法会产生 UX 和 UI 后果,您将需要使用进度或活动指示器来确保用户知道任务正在被处理。在任何情况下,此方法都不会占用应用的主线程,从而有助于主线程始终对用户交互作出快速响应。

避免微优化 JavaScript

知道浏览器执行一个函数版本比另一个函数要快 100 倍可能会很酷,比如请求元素的offsetTop比计算getBoundingClientRect()要快,但是,您在每帧调用这类函数的次数几乎总是很少,因此,把重点放在 JavaScript 性能的这个方面通常是白费劲。您一般只能节省零点几毫秒的时间。

如果您开发的是游戏或计算开销很大的应用,则可能属于本指南的例外情况,因为您一般会将大量计算放入单个帧,在这种情况下各种方法都很有用。

简而言之,慎用微优化,因为它们通常不会映射到您正在构建的应用类型。