2 posts tagged with "javascript"

View All Tags

前端监控

前端监控

一、为什么要做监控

  1. 更快发现问题并解决问题
  2. 做产品的决策依据
  3. 提升前端工程师的技术深度和广度,打造简历亮点
  4. 为业务拓展提供了更多可能性

二、前端监控目标

1、稳定性(stability)

错误名称备注
JS错误JS执行错误或者promise异常
资源异常script或者link资源加载异常
接口错误ajax或者fetch请求接口异常
白屏页面空白
// js执行序错误和资源加载 错误可以通过监听error事件来捕获
window.addEventListener('error', (e) => {
if (e.target && (e.target.src || e.target.href)) { // 资源加载错误
...
} else { // js执行错误
...
}
})
// promise异常, 可以通过监听unhandledrejection事件来捕获
window.addEventListener('unhandledrejection', (e) => {
console.log(e)
})

2、用户体验(experience)

错误名称备注
加载时间各个阶段的加载时间
fFB(tim to first byte)(首字节时间)是指浏览器发起第一个请求到数据返回第一个字节所消耗的时间,这个时间包含了网络请求时间、后端处理时间
FP(First Paint)(首次绘制)首次绘制包括了任何用户自定义的背景绘制,它是将第一个像素点绘制到屏幕的时刻
FCP(First Content Paint)(首次内容绘制)首次内容绘制是浏览器将第一个DOM渲染到屏幕的时间,可以是任何文本、图像、SVG等的时间
FMP(First Meaningful paint)(首次有意义绘制)首次有意义绘制是页面可用性的量度标准
FID(First Input Delay)(首次输入延迟用户首次和页面交互到页面响应交互的时间
卡顿超过50ms的长任务

3、业务(business)

错误名称备注
PVpage view即页面浏览量或点击量
UV指访问某个站点的不同IP地址的人数
页面的停留时间用户在每一个页面的停留时间

三、前端监控流程

  1. 前端埋点
  2. 数据上报
  3. 分析和计算 将采集到的数据进行加工和汇总
  4. 可视化展示 将数据按照各种维度进行展示
  5. 监控报警 发现问题后按照一定的条件触发报警

maidian

四、常见埋点方案

  1. 代码埋点
  • 就是以嵌入代码的形式进行埋点比如需要监控用户的点击事件,会选择在用户点击时,插入一段代码,保存这个监听行为或者直接将监听行为以某一种数据格式直接传递给服务器端
  • 优点:可以在任意时刻,精确的发送或保存所需要的数据信息;
  • 缺点:工作量较大
  1. 可视化埋点
  • 通过可视化交互的手段,代替代码埋点
  • 将业务代码和埋点代码分离,提供一个可视化交互的页面,输入为业务代码,通过这个可视化系统,可以在业务代码中自定义的增加埋点事件等等,最后输出的代码偶合了业务代码和埋点代码
  • 可视化埋点其实是用系统来代替手工插入埋点代码
  1. 无痕埋点
  • 前端的任意一个事件都被绑定一个标识,所有的事件都别记录下来
  • 通过定期上传记录文件,配合文件解析,解析出来我们想要的数据,并生成可视化报告供专业人员分析
  • 无痕埋点的优点是采集全量数据,不会出现漏埋和误埋等现象
  • 缺点是给数据传输和服务器增加压力,也无法灵活定制数据结构

react fiber

react fiber

在v15和之前的版本中,在react任意一个地方执行setState后,react会对整个页面创建虚拟dom,并对前后dom进行diff对比,然后进行渲染,这个过程是“一气呵成”的,所以它占据了主线程的大量时间,这会使页面响应度变差,也就导致了react在渲染动画,或者手势操作时会出现卡顿现象,因此react团队在react的v16版本后采用了fiber架构。

熟悉fiber之前需要先了解几个基础知识:window.requestIdleCallback单链表 image

帧率

我们知道屏幕浏览器刷新率60帧/s,平均16.6ms/帧,在一帧中浏览器做了很多事情:

在浏览器执行一帧的过程中:

  1. Input event handlers:合成线程 compositor thread 把 input 数据传给主线程, 处理事件回调。

  2. javascript: 包扩定时器、事件(scroll,resize等)、requestAnimationFrame、重排(layout)、重绘(paint)

  3. 如果在一帧内,执行完上述所有任务后,还有剩余时间的话,那就会执行requestIdleCallback回调,

window.requestIdleCallback(callback, options)
// callback(deadline): 一个function 用户要执行的回调任务, 并传入一个参数deadline
// deadline: {
// timeRemaining: 0ms, // 当前帧还剩余多少时间
// didTimeout: false, // 是否已超时
// }
// options: // 一个对象,可以设置timeout时间:超过这个时间后,不管当前是否有剩余时间必须执行此回调

因为requestIdleCallback兼容性差,所以react内部并不是直接用这个api,而是自己实现了这个api,理论效果是一样的

单链表

在fiber中有大量的单链表,用一张图表示:

image

重回fiber

1.通过fiber合理分配cpu资源,提高用户相应速度;2.通过fiber可以使reconciliation(一种diff算法)可以中断,交出主线程,去执行更重要的事情(渲染,交互等)

那么fiber是什么:fiber是一个执行单元,也是一种数据结构

执行单元fiber

每次浏览器执行完一个执行单元,react就会检查时候还有剩余时间(每一帧都会去检查),如果没有,react就放权给浏览器

image

数据机构fiber

react使用链表,将每一个VirtualDom节点及其内部所有子(不是孙子)节点表示为一个fiber。如图: image

其中A1>B1+B2就是一个fiber,B1>C1+C2是一个fiber

每个fiber其实就是一个对象,除了一些属性还包括三个指针

let fiber = {
tag: '', //当前节点类型,文本还是dom
key: 'ROOT', // 唯一标识
type: '', // 当前元素类型,span、div
stateNode: '', // fiber对应的node节点
flag: '', // placement等,副作用类型,例如: 增删改查
firstEffect: null,
lastEffect: null
// ...
// 三个指针
child: {}, // 指向当前第一个子fiber
sibling: {}, // 指向当前紧挨着的兄弟fiber
return: {}, // 指向当前的父fiber
}

react的构建过程

// 浏览器空闲时间执行
requestIdleCallback(workLoop) //react中是通过requestAnimationFrame和MessageChannel实现的
let rootFiber = {
...
}
let workInProgress = rootFiber //当前正在执行的工作单元(fiber)
function workLoop(deadLine) { // deadLine每帧剩余时间对象
while (workInProgress && deadLine.timeRemaining() > 1) {
workInProgress = performUnitOfWork(workInProgress) // 每个任务单元执行完毕后返回下一个要执行的任务单元
}
// 提交阶段
commitRoot(rootFiber)
}
function performUnitOfWork (workInProgress) {
beginWork(workInProgress) // 创建子fiber树
if (workInProgress.child) {
return workInProgress.child // 优先构建child
}
while (workInProgress) {
completeUnitWork(workInProgress) // 当前工作单元完成构建,并生成dom
if (workInProgress.sibling) {
return workInProgress.sibling // 没有child,构建sibling
}
workInProgress = workInProgress.return
// 最后没有父元素(root)退出循环
}
}
// 开始创建子Fiber树🌲
function beginWork (workInProgress) {
let nextChildren = workInProgress.props.children
return reconcileChildren(workInProgress, nextChildren)
}
function reconcileChildren (returnFiber, nextChildren) {
// 根据VDom生成fiber的同时并构建fiber链(就是给fiber的child,sibline, return属性赋值)
for (let i = 0; i < nextChildren.length; i++) {
let newFiber = createFiber(nextChildren[i])
// ...
}
}
// 创建fiber
function createFiber(element) {
return {
tag: TAG_HOST,
type: element.type,
props: element.props,
key: element.key,
// ...
}
}
function completeUnitWork (workInProgress) {
switch(workInProgress.tag) {
case TAG_HOST:
createStateNode(workInProgress) // 根据fiber生成真实dom节点
//...
}
// 完成时判断有没有对应的dom操作,有的话添加到副作用链表中
makeEffectList(workInProgress)
}
function makeEffectList (workInProgress) {
// 根据每个fiber的firstEffect和lastEffect以及flags
// 归并fiber树中各fiber的副作用,形成副作用链
// firstEffect -> nextEffect -> ... -> lastEffect
}
function commitRoot (rootFiber) {
let currentEffect = rootFiber.firstEffect
while (currentEffect) {
switch(currentEffect.flags) { // 副作用类型
case Placement:
commitPlacemen(currentEffect) // 向父dom添加子dom
}
currentEffect = currentEffect.nextEffect
}
}
function commitPlacemen (currentEffect) {
let parent = currentEffect.return.stateNode
parent.appendChild(currentEffect.stateNode)
}

总结

react将jsx经过createElement处理形成虚拟dom节点后的进行渲染,主要分为两个阶段: diff阶段和commit阶段: 在diff阶段进行新旧虚拟dom对比,进行更新,增量或者删除,并且根据虚拟dom生成fiber树

diff阶段可以暂停,因为diff阶段比较花时间,react会对任务进行拆分

commit阶段进行DOM的更新创建,此阶段不能暂停,需要“一气呵成”