vue diff算法理解

1. vue是怎么利用虚拟dom进行渲染优化的?

如果修改任意一个值都重刷整个页面,这样的开销将会十分的巨大。那么怎么精确快速的定位被修改节点呢?使用diff算法(该算法来源于snabbdom,复杂度为O(n))

2. 简单介绍一下vue中的diff算法实现

  function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    var oldStartIdx = 0;
    var newStartIdx = 0;
    var oldEndIdx = oldCh.length - 1;
    var oldStartVnode = oldCh[0];
    var oldEndVnode = oldCh[oldEndIdx];
    var newEndIdx = newCh.length - 1;
    var newStartVnode = newCh[0];
    var newEndVnode = newCh[newEndIdx];
    var oldKeyToIdx, idxInOld, vnodeToMove, refElm;

    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    var canMove = !removeOnly;

    {
      checkDuplicateKeys(newCh);
    }
    // 如果索引正常
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        // 当前的开始旧节点没有定义,进入下一个节点
      if (isUndef(oldStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
        // 当前的结束旧节点没有定义,进入上一个节点
      } else if (isUndef(oldEndVnode)) {
        oldEndVnode = oldCh[--oldEndIdx];
        // 如果旧的开始节点与新的开始节点相同,则开始更新该节点,然后进入下一个节点
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
     // 更新节点 patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue); oldStartVnode
= oldCh[++oldStartIdx]; newStartVnode = newCh[++newStartIdx]; // 如果旧的结束节点与新的开始节点相同,则开始更新该节点,然后进入下一个节点 } else if (sameVnode(oldEndVnode, newEndVnode)) { patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue); oldEndVnode = oldCh[--oldEndIdx]; newEndVnode = newCh[--newEndIdx]; // 如果旧的开始节点与新的结束节点相同,更新节点后把旧的开始节点移置节点末尾 } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue); canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)); oldStartVnode = oldCh[++oldStartIdx]; newEndVnode = newCh[--newEndIdx]; // 如果旧的结束节点与新的开始节点相同,更新节点后把旧的结束节点移置节点开头 } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue); canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm); oldEndVnode = oldCh[--oldEndIdx]; newStartVnode = newCh[++newStartIdx]; } else { // 如果旧的节点没有定义key,则创建key if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); } idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx); // 如果没有定义index,则创建新的新的节点元素 if (isUndef(idxInOld)) { // New element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm); } else { vnodeToMove = oldCh[idxInOld]; if (sameVnode(vnodeToMove, newStartVnode)) { patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue); oldCh[idxInOld] = undefined; canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm); } else { // same key but different element. treat as new element createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm); } } newStartVnode = newCh[++newStartIdx]; } } // 如果旧节点的开始index大于结束index,则创建新的节点 如果新的开始节点index大于新的结束节点则删除旧的节点 if (oldStartIdx > oldEndIdx) { refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm; addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue); } else if (newStartIdx > newEndIdx) { removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx); } }
检测newVnode与oldVnode是否相同
// 判断是不是相同的Vnode
function sameVnode (a, b) {
 // 如果key相同、都不是或者是注释节点、都有定义data、都是或者都不是input类型的
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}
// 如果是input类型 判断是否相同
function sameInputType (a, b) {
  if (a.tag !== 'input') { return true }
  var i;
  var typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type;
  var typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type;
  return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
}
// 创建key
function createKeyToOldIdx (children, beginIdx, endIdx) {
  var i, key;
  var map = {};
  for (i = beginIdx; i <= endIdx; ++i) {
    key = children[i].key;
    if (isDef(key)) { map[key] = i; }
  }
  return map
}

patchVnode更新oldVnode属性与内容(注意:如果鉴定oldVnode与Vnode相等(不是完全相等)只会更新oldVnode的props与text或者增删oldVnode,并不会更新oldVnode的data,所以使用index作为key有可能会造成一种情况请看实例:点击查看
  function findIdxInOld (node, oldCh, start, end) {
    for (var i = start; i < end; i++) {
      var c = oldCh[i];
      if (isDef(c) && sameVnode(node, c)) { return i }
    }
  }

  function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
// 如果oldVnode与vnode完全相等直接返回
if (oldVnode === vnode) { return } var elm = vnode.elm = oldVnode.elm; if (isTrue(oldVnode.isAsyncPlaceholder)) { if (isDef(vnode.asyncFactory.resolved)) { hydrate(oldVnode.elm, vnode, insertedVnodeQueue); } else { vnode.isAsyncPlaceholder = true; } return } // reuse element for static trees. // note we only do this if the vnode is cloned - // if the new node is not cloned it means the render functions have been // reset by the hot-reload-api and we need to do a proper re-render. // 如果是静态节点,key一致则直接使用旧节点的组件实例 if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance; return } var i; var data = vnode.data; // 更新旧节点的组件实例属性 if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode); } var oldCh = oldVnode.children; var ch = vnode.children; if (isDef(data) && isPatchable(vnode)) { for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); } if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); } } // 如果text为空 if (isUndef(vnode.text)) { //新旧子集都有定义 if (isDef(oldCh) && isDef(ch)) { // 如果新的子集不等于旧的子集则更新子集 if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); } } else if (isDef(ch)) { // 如果只有新子集有定义,则添加新的子集 if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); } else if (isDef(oldCh)) { // 如果只有旧的子集有定义则删除旧的子集 removeVnodes(elm, oldCh, 0, oldCh.length - 1); } else if (isDef(oldVnode.text)) { // 如果只有旧子集的text有定义 则把text置空 nodeOps.setTextContent(elm, ''); } // 如果旧的text不等于新的text则更新为新的text } else if (oldVnode.text !== vnode.text) { nodeOps.setTextContent(elm, vnode.text); } // 如果data有定义 if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldVnode, vnode); } } }

猜你喜欢

转载自www.cnblogs.com/dudeyouth/p/10725197.html