requestIdleCallback
- 功能: 利用浏览器的空余时间执行任务,如果有更高优先级的任务要执行时,当前执行的任务可以被终止,优先执行高级别任务。
requestidleCallback(function(deadline){
deadline.timeRemaining() // 获取浏览器空余时间
})
- 浏览器空余时间: 页面是一帧一帧绘制出来的,当每秒绘制的帧数达到 60 时,页面是流畅的,小于这个值时,用户会感觉到卡顿
1s 60帧,每一赖分到的时间是1000/60~16 ms,如果每一帧执行的时间小于16ms,就说明浏览器有空余时间
如果任务在剩余的时间内没有完成则会停止任务执行,继续优先执行主任务,也就是说requestldleCallback 总是利用浏览器的空余时间执行任务
- 例子:
<style>
.playground{
background: palevioletred;
padding:20px;
margin-bottom: 20px;
}
</style>
<body>
<div class="playground" id="play">playground</div>
<button id="work">work</button>
<button id="interaction">interaction</button>
</body>
<script>
let play = document.querySelector('#play');
let work = document.querySelector('#work');
let interaction = document.querySelector('#interaction');
let count = 100;
let expensive = id => {
while(count > 0 && id.timeRemaining() > 1){
count--;
console.log(count)
}
console.log('执行完成')
}
work.addEventListener('click',()=>{
requestIdleCallback(expensive)
})
interaction.addEventListener('click',()=>{
play.style.background = 'blue'
})
</script>
概念
- 问题 React 16 之前的版本比对更新 VirtualDOM 的过程是采用循环加递归实现的,这种比对方式有一个问题,就是一旦任务开始进行就无法中断,如果应用中组件数量庞大,主线程被长期占用,直到整棵 VirtualDOM 树比对更新完成之后主线程才能被释放,主线程才能执行其他任务。这就会导致一些用户交互,动画等任务无法立即得到执行,页面就会产生卡顿,非常的影响用户体验。
核心问题:递归无法中断,执行重任务耗时长。JavaScript 又是单线程,无法同时执行其他任务,导致任务延迟页面卡顿,用户体验差。
-
解决
1.利用浏览器空闲时间执行任务,拒绝长时间占用主线程
2.放弃递归只采用循环,因为循环可以被中断
3.任务拆分,将任务拆分成一个个的小任务
-
实现思路 在 Fiber 方案中,为了实现任务的终止再继续,DOM比对算法被分成了两部分:
1.构建 Fiber (可中断)
2.提交 Commit (不可中断)
DOM 初始渲染: virtualDOM -> Fiber -> Fiber[]->DOM
DOM 更新操作: newFiber vs oldFiber > Fiber[] ->DOM
-
Fiber 对象
{
type 节点类型(元素,文本,组件)(具体的类型)
props 节点属性
stateNode 节点 DOM 对象组件实例对象
tag 节点标记(对具体类型的分类 hostRoot || hostComponent || classComponent || functionComponent)
effects 数组,存储需要更改的 fiber 对象
effectTag 当前 Fiber 要被执行的操作(新增,删除,修改)
parent 当前 Fiber 的父级 Fiber
child 当前 Fiber 的子级 Fiber
sibling 当前 Fiber 的下一个兄弟 Fiber
alternate Fiber 备份 fiber 比对时使用
}
实现
- 创建任务队列
const root = document.createElement('root');
const jsx = (
<div />
)
render(jsx, root)
const taskQueue = createTaskQueue()
const render = (element, dom) => {
taskQueue.push({
dom,
props: { children: element }
})
requestIdleCallback(preformTask)
}
const createTaskQueue = () => {
const taskQueue = [];
return {
push: item => taskQueue.push(item),
pop: () => taskQueue.shift(),
// 判断队列是否还有任务
isEmp
}
}
- 实现认读调度
const preformTask = deadline => {
// 执行任务
workLoop(deadline)
// 判断任务是否存在且队列中是否还有任务
if(subTask || !taskQueue.isEmpty()){
requestIdleCallback(preformTask)
}
}
const workLoop = deadline => {
if(!subTask){
subTask = getFirstTask()
}
while(subTask && deadline.timeRemaining() > 1){
subTask = executeTask(subTask)
}
}
- 执行任务
// 构建根节点Fiber对象
const getFirstTask = () => {
const task = taskQueue.pop()
return {
props: task.props,
stateNode: task.dom,
tag: 'host_root',
effects: [],
child: null,
alterbate: task.dom.__rootFiberContainer
}
}
// 构建子节点Fiber对象
const executeTask = () => {}
- 实现初始渲染、更新节点
const commitAllWork = fiber => {
fiber.effects.forEach(item => {
if(item.effectTag === 'placement'){
item.parent.stateNode.appendChild(item.stateNode)
} else if(item.effectTag === 'update'){
if(item.type === iten.alternate.type){
updatsNodeElement(item.stateNode, item, item.alternate)
}else{
item.partent.stateNode.replaceChild(
item.stateNode,
item.alternate.stateNode,
)
}
}
})
// 备份旧的Fiber对象
fiber.stateNode.__rootFiberContainer = fiber;
}