实现任务调度器以及fiber架构

1. 实现任务调度器

(1)当我们执行一段js逻辑时,当运行的数据过多时,dom树就会特别大从而导致渲染卡顿,因为js是一个单线程语言,它在执行一个较长的逻辑时,就会阻塞我们一个后续的渲染,那么如何解决?

我们可以通过浏览器提供的一个API:requestIdleCallback

(2)我们通过调用requestIdleCallback,通过接收它传来的参数 deadline,调用deadline.timeReamaining()来查询浏览器的空余时间。我们就可以通过这个空余时间,将大任务拆分为多个小任务完成。

演示代码:

let taskId = 1
function workLoop(deadline) {
    // 获取浏览器空余时间
    console.log('+++++++',deadline.timeRemaining());
    taskId++
    let shouldYield = false;
    while(!shouldYield) {
        console.log('run task',taskId);
        shouldYield = deadline.timeRemaining() < 1
    }
    
    requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

2. 实现fiber架构

(1)我们的dom结构是一棵树,那么如何做到每次渲染几个节点,在下次执行的时候依然从之前的位置执行?我们可以将整个dom树转化为链表结构,并将其分为3个节点,孩子,兄弟,父亲节点。从上往下依次查找进行转化,如图:

流程关系:跟据树形结构将其转化为链表结构。先渲染A节点,看A节点是否有孩子节点,到B节点渲染,继续找孩子节点,到d节点,发现d节点没有孩子节点,找兄弟节点e,然后找叔叔节点c,然后孩子f,兄弟g节点,最后结束。采用先找child -> sibling兄弟 -> 叔叔的关系去进行链表结构。

那么,问题来了,我们是浏览器渲染时,现将整个树转化为链表结构再进行渲染处理,还是边转化着边处理,毋庸置疑,,第二种方法性能是最优的。

(2)将任务调度器引入到我们的代码中,在任务调度器的while循环里执行我们的任务nextWorkOfUnit,定义performWorkOfUnit方法用于执行渲染节点的逻辑。它会返回一个新的nextWorkOfUnit,这个就是一个新的指针,会指向在上一次浏览器的空余时间内执行任务的最后一项,在浏览器最新的时间空余出来时,接着最新的指针往下执行。

(3)接着我们要做的就是在performWorkOfUnit方法中,1.创建dom,2.处理props,3.将树形结构转化为链表结构,设置好指针,4.返回下一个要执行的任务。

        1. 创建dom        

function createDom(type) {
  return type === 'TEXT_ELEMENT' ? 
    document.createTextNode("") : 
    document.createElement(type)
}

        2. 处理props

function updateProps(props,dom) {
  for (const key in props) {
    if(key !== 'children') {
      dom[key] = props[key]
    }
  } 
}

        3. 将树形结构转化为链表结构

function initChildren(fiber) {
  const children = fiber.props.children
  let prevChild = null
  children.forEach((child,index) => {
    // 由于我们要给孩子节点定义父亲等属性,所以定义新的对象,这样可以不直接破坏之前vdom的结构
    const newFiber = {
      type:child.type,
      props:child.props,
      child:null,
      parent:fiber,
      sibling:null,
      dom:null
    }
    if(index == 0) {
      fiber.child = newFiber
    } else {
      prevChild.sibling = newFiber
    }
    prevChild = newFiber
  });
}

        4. 返回下一个要执行的任务

// 4.返回写一个要执行的任务
  if(fiber.child) {
    return fiber.child
  }
  if(fiber.sibling) {
    return fiber.sibling
  }
  return fiber.parent?.sibling

(4)对于render函数的优化以及修改

function render(el,container) {
  nextWorkOfUnit = {
    dom:container,
    props:{
      children:[el]
    }
  }
}

(5)任务调度器的修改

let nextWorkOfUnit = null
function workLoop(deadline) {
    let shouldYield = false;
    while(!shouldYield && nextWorkOfUnit) {

      nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit)
      shouldYield = deadline.timeRemaining() < 1

    }
    
    requestIdleCallback(workLoop)
}

(6)总结:1. 递归代码在较大数据量的情况下比较容易造成卡顿。递归代码需要一次性执行完,而且数据量特别大的时候容易出现爆内存。为了解决递归的问题,一般都会将递归代码转成遍历代码。对于 dom 树则需要转成链表结构进行遍历,边执行边转换的技巧也比较优雅。再结合指针,使得链表遍历可以随时停止并恢复。2.使用 requestIdleCallback 实现调度器,利用浏览器空闲时间进行 dom 元素的渲染。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值