• vue虚拟dom和diff算法


    vue的虚拟dom和diff算法

    1.虚拟dom

    虚拟dom,我的理解就是通过js对象的方式来具体化每一个节点,把dom树上面的每个节点都变为对象里的一个元素,元素的子元素变为子节点,节点上面的class、id、attribute等属性变为data内的值,然后通过dom上面的createElement、appendChild、insertBefore等方法进行生成dom树。

    let VNode = {
        sel:'div',
        data:{
            key:0,
            props:{},
            attrs:{},
            class:{},
            style:{},
            fn:{}
        },
        text:'虚拟dom',
        elm:'<div>虚拟dom</div>'
        children:[
            {
                sel:'div',
                data:{
                    key:0,
                    props:{},
                    attrs:{},
                    class:{},
                    style:{},
                    fn:{}
                },
                text:'虚拟dom children',
                elm:'<div>虚拟dom children</div>'
                children:[]
            }
        ]
    }

     2.diff算法

    看了diff算法后感觉写的真是巧妙,真正做到了最小量更新 。

    diff是当父节点相同时用来对子节点进行最小量更新的算法。

    diff算法采用四个指针:旧节点开始指针,旧节点结束指针,新节点开始指针,新节点结束指针;

    (上方虚拟节点中的key就是为了在进行diff算法时判断是否是同一个节点便于最小量更新)

    while(旧节点开始指针<=旧节点结束指针&&新节点开始指针<=新节点结束指针){

    分为以下五种情况:(前四种情况)

      当进行下面5种判断后可能会出现新节点[新节点开始指针]   旧节点[旧节点开始指针]   新节点[新节点结束指针]   旧节点[旧节点结束指针]为空值的情况,如果出现空值则代表当前节点已经处理过了,所以就需要将指针++或者--

    if(旧节点[旧节点开始指针] ==null){

    旧节点开始指针++

    }else if(旧节点[旧节点结束指针]==null){

    旧节点结束指针--

    }else if(新节点[新节点开始指针] ==null){

    新节点开始指针++

    }else if(新节点[新节点结束指针] ==null){

    新节点结束指针--

    }

    1、新节点[新节点开始指针] 对比  旧节点[旧节点开始指针]

      如果符合此种情况,则代表新节点[新节点开始指针] 旧节点[旧节点开始指针] 为同一个节点,实行最小量更新,即只更新节点内的属性而不进行dom销毁创建操作,完成更新后 新节点开始指针++旧节点开始指针++

    2、新节点[新节点结束指针] 对比 旧节点[旧节点结束指针]

      如果符合此种情况,则代表新节点[新节点结束指针] 旧节点[旧节点结束指针] 为同一个节点,实行最小量更新,即只更新节点内的属性而不进行dom销毁创建操作,完成更新后 新节点结束指针--旧节点结束指针--

    3、新节点[新节点结束指针] 对比 旧节点[旧节点开始指针]

      如果符合此种情况,则代表新节点[新节点结束指针] 旧节点[旧节点开始指针] 为同一个节点,实行最小量更新,先更新节点内的属性,然后使用insertBefore将旧节点[旧节点开始指针] 移动到旧节点[旧节点结束指针] 之后,(注意:此处要移动到旧节点[旧节点结束节点] 后,而不是所有旧节点后,因为这里的旧节点结束指针是会变化的),

        父节点.insertBefore(旧节点[旧节点开始指针].elm, 旧节点[旧节点结束指针].elm.nextSibling)

      完成操作后 新节点结束指针--旧节点开始指针++

    4、新节点[新节点开始指针] 对比 旧节点[旧节点结束指针] 如果符合此种情况,则代表新节点[新节点开始指针]旧节点[旧节点结束指针] 为同一个节点,实行最小量更新,先更新节点内的属性,然后使用insertBefore将旧节点[旧节点结束指针] 移动到旧节点[旧节点开始指针] 前,(注意:此处要移动到旧节点[旧节点开始指针] 前,而不是所有旧节点前,因为旧节点开始指针也是会发生变化的)

        父节点.insertBefore(旧节点[旧节点结束指针].elm, 旧节点[旧节点开始指针].elm)

       完成操作后,旧节点结束指针--新节点开始指针++

    5、遍历旧节点数组,生成一个以key为键,index为值的对象为旧节点keyIndexMap,然后查询新节点[新节点开始指针]中的key是否在旧节点keyIndexMap中存在;

      如果不存在,则证明新节点[新节点开始指针]在旧节点列表中不存在,此时需要创建新节点[新节点开始指针]为真实dom,并将其插入至旧节点[旧节点开始指针]前(因为此时新节点[新节点开始指针]一定处于全部未处理的旧节点前)

    父节点.insertBefore(创建dom(新节点[新节点开始指针]), 旧节点[旧节点开始指针].elm)

      如果存在则先需要判断旧节点[旧节点keyIndexMap[新节点[新节点开始指针][key]]]新节点[新节点开始指针]的sel(标签)是否相同:

        如果相同则代表为同一个标签,则进行最小量更新,先更新节点内的属性,然后insertBefore将旧节点[旧节点keyIndexMap[新节点[新节点开始指针][key]]]移动到旧节点[旧节点开始指针] 前,然后将旧节点[旧节点keyIndexMap[新节点[新节点开始指针][key]]]设置为undefined,代表当前节点处理过了;

        如果不同则代表不是同一个标签,则只创建新节点[新节点开始指针]的真实dom,然后将其插入到旧节点[旧节点开始节点]

      最后新节点开始指针++

    }

    当以上循环完成后可能还会出现没有处理到的节点,所以还需要再查找没有处理到的节点:

      如果是新节点开始指针<=新节点结束指针,则代表新节点列表内还有没有处理的节点,没有处理的节点全部为新增节点,此时需要遍历新节点[新节点开始指针](包含)至新节点[新节点结束指针](包含)之间的节点,然后将其添加至新节点[新节点结束指针+1]之前(新节点[新节点结束指针+1]可能为空,新节点[新节点结束指针+1]为空时可添加到最后)

            for (let i = 新节点开始节点; i <= 新节点结束节点; i++) {
              //insertBefore可以自动识别空值,如果是空值,则插入到最后
              父节点.insertBefore(创建dom(新节点[i]), 新节点[新节点结束节点-1]?.elm)
            }

       如果是旧节点开始指针<=旧节点结束指针,则代表旧节点内还有没有处理的节点,没有处理的节点全部为需要删除节点,此时需要遍历旧节点[旧节点开始指针](包含)至旧节点[旧节点结束指针](包含) 之间的节点,然后将其全部删除。

            for (let i = 旧节点开始指针; i <= 旧节点结束指针; i++) {
              旧节点[i] && (父节点.removeChild(旧节点[i].elm))
            }

    以上就是我对diff算法的理解,下面贴上代码(阉割版,部分情况没有考虑,旨在学习diff算法,可能会有bug):

    //updateChildren文件
    import { sameVnode } from './is'
    import patchVnode from './patchVnode'
    import createElement from './createElement'
    
    export default functionupdateChildren  (parentElm, oldCh, newCh) {
      console.log('updateChildren')
      console.log(parentElm, oldCh, newCh)
    
      //旧前
      let oldStartIdx = 0
      //新前
      let newStartIdx = 0
      //旧后
      let oldEndIdx = oldCh.length - 1
      //新后
      let newEndIdx = newCh.length - 1
      //旧节点
      let oldStartVnode = oldCh[0]
      //旧后节点
      let oldEndVnode = oldCh[oldEndIdx]
      //新节点
      let newStartVnode = newCh[0]
      //新后节点
      let newEndVnode = newCh[newEndIdx]
    
      let keyMap = {}
    //  开始循环
      while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        // debugger
        console.log('while')
        if (oldStartVnode === undefined || oldCh[oldStartIdx] === undefined) {
          oldStartVnode = oldCh[++oldStartIdx]
        } else if (oldEndVnode === undefined || oldCh[oldEndIdx] === undefined) {
          oldEndVnode = oldCh[--oldEndIdx]
        } else if (newStartVnode === undefined || newCh[newStartIdx] === undefined) {
          newStartVnode = newCh[++newStartIdx]
        } else if (newEndVnode === undefined || newCh[newEndIdx] === undefined) {
          newEndVnode = newCh[--newEndIdx]
        } else if (sameVnode(oldStartVnode, newStartVnode)) {
          //新前和旧前是同一个节点
          console.log('新前和旧前是同一个节点')
          patchVnode(oldStartVnode, newStartVnode)
          oldStartVnode = oldCh[++oldStartIdx]
          newStartVnode = newCh[++newStartIdx]
        } else if (sameVnode(oldEndVnode, newEndVnode)) {//旧后和新后是同一个节点
          console.log('旧后和新后是同一个节点')
          patchVnode(oldEndVnode, newEndVnode)
          oldEndVnode = oldCh[--oldEndIdx]
          newEndVnode = newCh[--newEndIdx]
        } else if (sameVnode(oldStartVnode, newEndVnode)) {//新后和旧前是同一个节点
          console.log('新后和旧前是同一个节点')
          patchVnode(oldStartVnode, newEndVnode)
          //当新后节点是旧前节点时,此时需要移动节点,移动旧前节点到旧后的后面
          parentElm.insertBefore(oldStartVnode.elm, oldEndVnode.elm.nextSibling)
          oldStartVnode = oldCh[++oldStartIdx]
          newEndVnode = newCh[--newEndIdx]
        } else if (sameVnode(oldEndVnode, newStartVnode)) {//旧后和新前是同一个节点
          console.log('旧后和新前是同一个节点')
          //  当旧后和新前是同一个节点时,此时需要移动旧后节点到旧前节点的前面
          patchVnode(oldEndVnode, newStartVnode)
          parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm)
          oldEndVnode = oldCh[--oldEndIdx]
          newStartVnode = newCh[++newStartIdx]
        } else {
          //前四种都没有命中
          if (Object.keys(keyMap).length === 0) {
            keyMap = {}
            for (let i = oldStartIdx; i <= oldEndIdx; i++) {
              const key = oldCh[i].key
              if (key) {
                keyMap[key] = i
              }
            }
          }
          console.log(keyMap)
          //寻找当前节点在keyMap中的位置
          const idxInOld = keyMap[newStartVnode.key]
          console.log(idxInOld)
          if (!idxInOld) {
            //新节点不在旧节点中
            parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm)
          } else {
            //  新节点在旧节点中,需要移动
            const elmToMove = oldCh[idxInOld]
            if (elmToMove.sel !== newStartVnode.sel) {
              parentElm.insertBefore(createElement(newStartVnode), oldStartVnode.elm)
            } else {
              patchVnode(elmToMove, newStartVnode)
              //  把这项设置为undefined,表示已经移动过了
              oldCh[idxInOld] = undefined
              parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm)
            }
          }
          //指针向后移动
          newStartVnode = newCh[++newStartIdx]
        }
      }
    // 继续查询是否有剩余节点
      if (newStartIdx <= newEndIdx) {
        console.log('新节点还有剩余节点没有处理', newStartIdx, newEndIdx)
        const before = newCh[newEndIdx + 1]?.elm
        console.log(before)
        for (let i = newStartIdx; i <= newEndIdx; i++) {
          //insertBefore可以自动识别undefined,如果是undefined,则插入到最后
          parentElm.insertBefore(createElement(newCh[i]), before)
        }
      } else if (oldStartIdx <= oldEndIdx) {
        console.log('旧节点还有剩余节点没有处理', oldStartIdx, oldEndIdx)
        for (let i = oldStartIdx; i <= oldEndIdx; i++) {
          oldCh[i] && (parentElm.removeChild(oldCh[i].elm))
        }
      }
    }
    
    let arr = [1, 1, 2, 35, 9, 2, 9]
    arr.reduce((p, n) => {
      return p ^ n
    }, 0)
    //is文件
    export function sameVnode (vnode1, vnode2) {
      return vnode1.sel === vnode2.sel && vnode1.key === vnode2.key;
    }
    //createElement文件
    //真正创建dom
    export default function createElement (vnode) {
      let domNode = document.createElement(vnode.sel);
      if (
        vnode.text !== "" &&
        (vnode.children === undefined || vnode.children.length === 0)
      ) {
        domNode.innerText = vnode.text;
        //  补充elm
    
      } else if (Array.isArray(vnode.children) && vnode.children.length > 0) {
        for (let i = 0; i < vnode.children.length; i++) {
          domNode.appendChild(createElement(vnode.children[i]));
        }
      }
      vnode.elm = domNode;
      return vnode.elm
    }
    //patchVnode文件
    import createElement from './createElement'
    import updateChildren from './updateChildren'
    
    export default function patchVnode (oldVnode, newVnode) {
      // console.log('patchVnode')
      if (oldVnode === newVnode) return
      if (newVnode.text && (!newVnode.children || newVnode.children.length === 0)) {//判断newVnode的text是否为空,且不等于oldVnode的text,如果满足以上条件,则更新text
        oldVnode.text !== newVnode.text && (oldVnode.elm.innerText = newVnode.text);
      } else {//newVnode的text为空,则判断newVnode的children是否为空,如果不为空,则更新children
        //  新节点没有text属性
        if (oldVnode.children && oldVnode.children.length > 0) {
          //  老节点有children,新节点也有children
          updateChildren(oldVnode.elm, oldVnode.children, newVnode.children);
        } else {
          //  老的没有children,新的有children
          oldVnode.elm.innerHTML = '';
          for (let i = 0; i < newVnode.children.length; i++) {
            let dom = createElement(newVnode.children[i])
            oldVnode.elm.appendChild(dom)
          }
        }
      }
    }
    //patch文件
    import vnode from "./vnode";
    import createElement from "./createElement";
    import patchVnode from './patchVnode'
    import { sameVnode } from './is'
    
    export default function (oldVnode, newVnode) {
      // console.log(oldVnode, newVnode)
      //判断传入的第一个参数,是dom节点还是vnode
      if (oldVnode.sel === "" || oldVnode.sel === undefined) {
        //传入的如果是dom节点需要包装为虚拟节点
        oldVnode = vnode(
          oldVnode.tagName.toLowerCase(),
          {},
          [],
          undefined,
          oldVnode,
        );
      }
      //  判断oldVnode和newVnode是否是同一个节点
      if (sameVnode(oldVnode, newVnode)) {
        // console.log("是同一个节点");
        patchVnode(oldVnode, newVnode);
      } else {
        // console.log("不是同一个节点");
        let newVnodeElm = createElement(newVnode, oldVnode.elm);
        if (oldVnode.elm.parentNode && newVnodeElm) {
          oldVnode.elm.parentNode.insertBefore(newVnodeElm, oldVnode.elm);
        }
        oldVnode.elm.parentNode.removeChild(oldVnode.elm);
      }
    }
    //VNode文件
    export default function (sel, data, children, text, elm) {
      const key = data.key
      return {
        sel,
        data,
        children,
        text,
        elm,
        key,
      }
    }
    //h文件
    
    import vnode from './vnode.js'
    
    //h('div',{},'文字')
    //h('div',{},'[]')
    //h('div',{},h())
    export default function (sel, data, c) {
    //检查参数个数
      if (arguments.length !== 3) {
        throw new Error('h()参数个数不正确')
    
      }
    //    检查C类型
      if (typeof c === 'string' || typeof c === 'number') {
        return vnode(sel, data, undefined, c, undefined)
      } else if (Array.isArray(c)) {
        let children = []
        for (let i = 0; i < c.length; i++) {
          if (!(typeof c[i] === 'object' && c[i].hasOwnProperty('sel'))) {
            throw new Error('传入的数组参数中有项不是h函数')
          }
          children.push(c[i])
        }
        //  循环结束,children收集完毕
        return vnode(sel, data, children, undefined, undefined)
      } else if (typeof c === 'object' && c.hasOwnProperty('sel')) {
        let children = [c]
        return vnode(sel, data, children, undefined, undefined)
      } else {
        throw new Error('h()参数类型不正确')
      }
    }
  • 相关阅读:
    扩欧(exgcd讲解)
    Django组件之forms
    Django组件之用户认证
    Django之中间件
    Django之cookie与session
    Django组件之分页器
    Django之Ajax
    Django之模型层2
    Django之模型层
    Django之模板层
  • 原文地址:https://www.cnblogs.com/cuteyuchen/p/16372713.html
Copyright © 2020-2023  润新知