基本概念
-
屏幕刷新率
目前大多数设备每一秒刷新60次,每一次称作1帧,每一帧的执行时间大约是16ms,所以当每一帧的执行时间不大于16ms,才能保证不出现卡顿情况 -
帧生命周期 life of frame
- 处理用户事件(输入、点击、滚轮、滑动等)
- 执行js代码
- 开始帧
- 执行requestAnimationFrame回调
- 布局
- 绘制
-
帧空闲时间(requestIdleCallback) 16ms
-
requestAnimationFrame
<div id="div" style="width:0;background:red;height:20px;margin-bottom: 10px;"></div> <button id="btn">开始</button> <script> // 通过requestAnimationFrame绘制进度条 const div = document.getElementById('div'); let lastTime; function progress() { const current = Date.now(); console.log('时间差:', current - lastTime); lastTime = current; const width = parseInt(div.style.width); if(width<100) { div.style.width = width + 1 + '%'; div.innerText = div.style.width; requestAnimationFrame(progress); } } document.getElementById('btn').addEventListener('click', () => { lastTime = Date.now(); div.style.width = 0; progress(); }) </script>
示例
-
react中的基础数据结构:链表
以下代码模拟了一个单链表数据结构的update操作,依次执行每一个update,最终得到最终的结果// 每一个更新单元,单元中包含的载荷数据、以及下一个更新单元的指针 class Update { constructor(payload) { this.payload = payload; this.nextUpdate = null; } } // 更新队列,包含一个首节点、和一个尾结点 class UpdateQueue { constructor() { this.firstUpdate = null; this.lastUpdate = null; } // 追加更新单元到队列 enqueueUpdate(update) { if (!this.firstUpdate) { this.firstUpdate = this.lastUpdate = update; } else { this.lastUpdate.nextUpdate = update; this.lastUpdate = update; } } // 执行队列 forceUpdate() { let curUpdate = this.firstUpdate; let state = {}; while (curUpdate) { const curState = typeof curUpdate.payload === 'function' ? curUpdate.payload(state) : curUpdate.payload; state = { ...state, ...curState }; curUpdate = curUpdate.nextUpdate; } return state; } } const queue = new UpdateQueue(); let update = new Update({ name: 'kerry' }); queue.enqueueUpdate(update); update = new Update({ age: 20 }); queue.enqueueUpdate(update); update = new Update(state => ({ age: state.age + 1 })); queue.enqueueUpdate(update); update = new Update(state => ({ name: state.name + 'is good' })); queue.enqueueUpdate(update); // 最终结果: { name: 'kerryis good', age: 21 } console.log(queue.forceUpdate());
fiber之前的react
babel在编译jsx代码的时候,会将其转换成React.createElement方法,运行时得到一个虚拟dom树
react会递归比对虚拟dom树,找出需要变动的节点,同步更新它们。这个过程称之为Reconcilation(协调)
在协调期间,React会一直占用浏览器资源,不能够中断,当一个项目足够大的时候,会有很深的调用栈,而且长时间无法释放浏览器资源,造成卡顿
const root = {
key: 'a1',
children: [
{
key: 'b1',
children: [
{
key: 'c1',
children: [],
},
{
key: 'c2',
children: [],
}
]
},
{
key: 'b2',
children: []
}
]
}
walk(root);
function walk(vDom) {
doWork(vDom);
vDom.children.forEach(child => {
walk(child);
})
}
function doWork(vDom) {
console.log(vDom.key);
}
fiber是什么
- 是一种调度策略,合理的去分配资源。让Reconcilation变得更协调,让React的协调过程变得可以随时中断,适时的让出主线程去响应高优先级的用户操作。
-
是一个执行单元,每次执行完一个执行单元,react就会检查当前帧还有多少剩余时间,及时释放资源
image.png - 是一种链表形式的数据结构,virtualDom的每一个节点就是一个fiber
fiber的重要属性
type
:类型
childe
:第一个子节点
sibling
:下一个弟弟节点
return
:父节点
fiber链表结构描述了上述root结构的一个虚拟dom树
fiber执行阶段
- 每一次的渲染有两个阶段:协调阶段(render执行得到一个fiber树和Reconciliation)和提交阶段(commit)
-
协调阶段:实质上就是一个diff操作,这个阶段会遍历找出所有需要变更(增删改)的节点,可以被中断,这些变更被称之为react的副作用(effect)
遍历规则:深度优先遍历。从根节点出发,对于每一个节点来说优先遍历child,没有child找sibling,没有sibling,没有sibling找return的sibling。(遵循皇位继承原则,长子优先->次子->弟弟)。
所以上述root节点的遍历顺序为:A1->B1->C1->C2->B2
let nextUnitWork = null; function workLoop(deadline) { // 如果存在待执行单元,切当前帧剩余时间大于5ms或超时,则执行待执行单元 while ((deadline.timeRemaining() > 5 || deadline.didTimeout) && nextUnitWork) { nextUnitWork = performUnitWork(nextUnitWork); } if (!nextUnitWork) { console.log('render阶段结束'); } else { requestIdleCallback(workLoop, { timeout: 1000 }); } } function performUnitWork(fiber) { beginWork(fiber); if (fiber.child) return fiber.child; // 没有child 当前fiber执行完成,并找到父fiber的兄弟节点 while (fiber) { completeWork(fiber); if (fiber.sibling) return fiber.sibling; fiber = fiber.return; } } function beginWork(fiber) { console.log('当前执行:' + fiber.key); } function completeWork(fiber) { console.log('执行完成:' + fiber.key); } nextUnitWork = root; // 调用requestIdleCallback利用帧剩余时间去做协调diff requestIdleCallback(workLoop, { timeout: 1000 });
提交阶段:对协调阶段的产物(需要变更的节点)进行提交执行,不可以被中断,必须同步一次性
-