Diff算法

由于在浏览器中操作DOM的代价是非常大的,所以在Vue引入了虚拟DOM,虚拟DOM是对真实DOM的一种抽象描述。将虚拟DOM渲染成真实DOM并挂载到页面中,将虚拟DOM渲染成真实DOM的逻辑也比较简单。

主要步骤就是:创建节点→设置节点属性→添加子节点

DOM-diff比较两个虚拟DOM的区别,也就是在比较两个对象的区别。
作用: DOM-diff 算法主要功能是收集新老两个节点之间的变化,然后根据节点的变化来更新DOM。

// diff.js
function diff(oldNode, newNode) {
  // 收集两个VDOM节点之间的不同

  // 递归比较两个虚拟DOM,将比较结果放入Pathes中
  let patches = walk(oldNode, newNode)

  return patches
}


function walk(oldNode, newNode) {
  let currentPatch = {} // 每一个DOM元素都有一个patch

  if (!newNode) {
    // 如果不存在newNode,说明该Node被移除
    currentPatch.diff = {type: 'REMOVE', oldNode}
  } else if(newNode && !oldNode) {
    // 说明是新结点
    currentPatch.diff = {type: 'ADD', newNode}
  } else if (isString(oldNode) && isString(newNode)) {
    // 如果是文本, 则判断文本是否一致
    if (oldNode !== newNode) {
      currentPatch.diff = {type: 'TEXT', newNode}
    }
  } else if (oldNode.type === newNode.type) {
    // 如果是虚拟DOM元素
    // 比较属性是否相同
    let attrDiff = diffAttr(oldNode.props, newNode.props)
    if (Object.keys(attrDiff).length > 0) {
      currentPatch.diff = {type: 'ATTR', attrDiff}
    } else {
      // 说明该元素的属性相同, 可以理解为node没有被修改
      currentPatch.diff = {}
    }
    // 比较子节点是否相同
    currentPatch.children = []
    diffChildren(oldNode.children, newNode.children, currentPatch.children)
  } else if(oldNode.type !== newNode.type) {
    // 否则说明节点被替换了
    currentPatch.diff = {type: 'REPLACE', newNode}
  } else {
    // 说明没有被修改, 添加一个空对象
    currentPatch.diff = {}
  }

  return currentPatch
}


function isString(obj) {
  return obj && typeof obj === 'string'
}

function diffAttr(oldAttrs, newAttrs) {
  let patch = {}
  // 新老属性是否相同
  for (let key in oldAttrs) {
    if (oldAttrs[key] !== newAttrs[key]) {
      patch[key] = newAttrs[key]
    }
  }

  // 老节点中没有但新结点中有的属性
  for (let key in newAttrs) {
    if (!oldAttrs.hasOwnProperty(key)) {
      patch[key] = newAttrs[key]
    }
  }

  return patch
}

function diffChildren(oldChildren, newChildren, childrenPatch) {
  // 比较子节点
  newChildren.forEach((item, i) => {
    childrenPatch.push(walk(oldChildren[i], item))
  })
}

主要工作就是收集每个节点是否产生了变化,主要做了一下工作来得到两个节点的不同, 最终得到的patch的结构和虚拟DOM结构一样都是一个树状的结构。

节点更新

// patch.js
function patch(node, patch) {
  run(node, patch)
}

/**
 * 
 * @param {HTMLElement} node 节点
 * @param {Object} patches 节点之间的不同
 * @param {Number} index 节点树的第几层
 * @param {Number} i 该层的第几个节点
 */
function run(node, patch) {
  let childNodes = node.childNodes
  let patchChildren = patch.children
  // 先修改每个子节点
  for (let i = 0; i < childNodes.length; ++i) {
    run(childNodes[i], patchChildren[i])
  }
  
  // patcheChildren的长度大于childNodes的长度, 说明有添加了新的节点
  if (patchChildren && patchChildren.length && patchChildren.length > childNodes.length) {
    for (let i = childNodes.length; i < patchChildren.length; ++i) {
      doPatch(node, patchChildren[i])
    }
  }
  if (patch) {
    doPatch(node, patch)
  }
}

function doPatch(node, patch) {
  console.log(Object.keys(patch))
  if (Object.keys(patch).length > 0) {
    let { diff } = patch
    switch(diff.type) {
      case 'ADD':
        var newNode = diff.newNode
        newNode = newNode instanceof Element ? render(newNode) : document.createTextNode(newNode)
        node.appendChild(newNode)
        break
      case 'ATTR':
        for (let key in diff.attrDiff) {
          let value = diff.attrDiff[key]
          setAttr(node, key, value)
        }
        break
      case 'TEXT':
        node.textContent = diff.newNode
        break
      case 'REPLACE':
        var newNode = diff.newNode
        let replaceNode = newNode instanceof Element ? render(newNode) : document.createTextNode(newNode)
        node.parentNode.replaceNode(replaceNode, node)
        break
      case 'REMOVE':
        node.parentNode.removeChild(node)
        break
    }
  }
}

patch内部只是简单的调用了run方法

run方法中做了两件事

深度优先当前节点子节点先对子节点进行修改
如果patch.children的长度大于当前节点的子节点的长度,说明添加了一个新的元素,调用doPatch方法将新添加的元素加入当前节点
然后调用doPatch方法对当前节点进行处理
doPatch方法主要是根据修改的类型对node节点进行处理。

总结:
整个DOM-diff的过程:

用JS对象模拟DOM(虚拟DOM)

把此虚拟DOM转成真实DOM并插入页面中(render)

如果有事件发生修改了虚拟DOM,比较两棵虚拟DOM树的差异,得到差异对象(diff)

把差异对象应用到真正的DOM树上(patch)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值