在上一章中,要思考一个问题,就是如果dom结构过于复杂的话,渲染dom的时候页面就会变得卡顿,这是因为js是单线程的语言,如果有js代码执行的话,dom渲染就会停滞,等待其他的代码执行完。如果要解决这个问题的话,可以实现一个任务调度器,让浏览器在空闲时间去渲染dom。

实现任务调度器

window.requestIdleCallback() 可以用来监测浏览器是否有空闲时间,所以可以基于这个api来实现任务调度器

window.requestIdleCallback() 可以接受一个函数,函数中会返回一个[IdleDeadline](<https://developer.mozilla.org/zh-CN/docs/Web/API/IdleDeadline>) ,通过判断 IdleDeadline() < 1 就能判断浏览器是否还有空闲时间

function workLoop(deadline){
    let shouldYield = true
		while(isWorkOfUtil){
        // to do things...

        shouldYield = idleDeadline.timeRemaining() < 1
    }

    requestIdleCallback(workLoop)
}

实现fiber架构

采用任务调度器的方式来进行dom渲染的话,需要将dom树转化成链表的格式来进行操作,让任务去一个一个的执行。

Untitled

在转化的时候需要定制规则:

  1. 先找child节点。
  2. 没有child节点,找sibling节点。
  3. 最后找叔叔节点,即父节点的sibling节点。

Untitled

代码实现:

  1. 重构render方法,在render方法中给fiberOfUnit赋值:
function render(el, container){
    fiberOfUnit = {
        dom: container,
        props: {
            children: [ el ]
        }
    }
}
  1. 结合任务调度器,在空闲时间进行dom渲染
let fiberOfUnit = null
function workLoop(idleDeadline){
    let shouldYield = true
		// 利用while来监控空闲时间的剩余,用来决定是否要进行dom渲染
    while(shouldYield && fiberOfUnit){
        fiberOfUnit = perfromFiberOfUnit(fiberOfUnit)

        shouldYield = idleDeadline.timeRemaining() < 1
    }
		
		// 递归调用api,检查浏览器是否有空闲时间
    requestIdleCallback(workLoop)
}
  1. 首先根据上面的转化规则,可以明确的知道每一个vdom都需要child、sibling、parent以及挂载用的dom节点,但是如果直接在之前设计好的vdom上修改的话,会破坏原有的结构,所以需要新建一个对象来表示。