深入理解Vue2.x的虚拟DOM diff原理

前端之家收集整理的这篇文章主要介绍了深入理解Vue2.x的虚拟DOM diff原理前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

前言

经常看到讲解Vue2的虚拟Dom diff原理的,但很多都是在原代码的基础上添加些注释等等,这里从0行代码开始实现一个Vue2的虚拟DOM

实现VNode

src/core/vdom/Vnode.js

标签名 children,//孩子[VNode,VNode],text,//文本节点 elm //对应的真实dom对象 ){ this.tag = tag; this.children = children this.text = text; this.elm = elm; } } export function createTextNode(val){ //为什么这里默认把elm置为undefined,不直接根据tag 用document.createElement(tagName)把elm赋值?而要等后面createElm时候再赋值呢? return new VNode(undefined,undefined,String(val),undefined) } export function createCommentNode(tag,children){ if(children){ for(var i=0;i

定义一个Vnode类, 创建节点分为两类,一类为text节点,一类非text节点

src/main.js

在main.js就可以根据Vnode 生成对应的Vnode对象,上述代码对应的dom表示

  • item1
  • item2
  • item3
  • 先实现不用diff把Vnode渲染到页面中来

    为什么先来实现不用diff渲染Vnode的部分,这里也是为了统计渲染的时间,来表明一个道理。并不是diff就比非diff要开,虚拟DOM并不是任何时候性能都比非虚拟DOM 要快

    先来实现一个工具函数,不熟悉的人可以手工敲下代码 熟悉下

    export function createElement (tagName) {
    return document.createElement(tagName)
    }

    export function createTextNode (text) {
    return document.createTextNode(text)
    }

    export function createComment (text) {
    return document.createComment(text)
    }

    export function insertBefore (parentNode,newNode,referenceNode) {
    parentNode.insertBefore(newNode,referenceNode)
    }

    export function removeChild (node,child) {
    node.removeChild(child)
    }

    export function appendChild (node,child) {
    node.appendChild(child)
    }

    export function parentNode (node) {
    return node.parentNode
    }

    export function nextSibling (node) {
    return node.nextSibling
    }

    export function tagName (node) {
    return node.tagName
    }

    export function setTextContent (node,text) {
    node.textContent = text
    }

    export function setAttribute (node,key,val) {
    node.setAttribute(key,val)
    }

    src/main.js

    var container = document.getElementById("app");
    var oldVnode = new VNode(container.tagName,[],container);
    var newVonde = createCommentNode('ul',['item 3'])])

    console.time('start');
    patch(oldVnode,newVonde); //渲染页面
    console.timeEnd('start');

    这里我们要实现一个patch方法,把Vnode渲染到页面

    src/core/vdom/patch.js

    export default function patch(oldVnode,vnode){
    let isInitialPatch = false;
    if(sameVnode(oldVnode,vnode)){
    //如果两个Vnode节点的根一致 开始diff
    patchVnode(oldVnode,vnode)
    }else{
    //这里就是不借助diff的实现
    const oldElm = oldVnode.elm;
    const parentElm = nodeOps.parentNode(oldElm);
    createElm(
    vnode,parentElm,nodeOps.nextSibling(oldElm)
    )
    if(parentElm != null){
    removeVnodes(parentElm,[oldVnode],0)
    }
    }
    return vnode.elm;
    }
    function patchVnode(oldVnode,vnode,removeOnly){
    if(oldVnode === vnode){
    return
    }
    const elm = vnode.elm = oldVnode.elm
    const oldCh = oldVnode.children;
    const ch = vnode.children

    if(isUndef(vnode.text)){
    //非文本节点
    if(isDef(oldCh) && isDef(ch)){
    //都有字节点
    if(oldCh !== ch){
    //更新children
    updateChildren(elm,oldCh,ch,removeOnly);
    }
    }else if(isDef(ch)){
    //新的有子节点,老的没有
    if(isDef(oldVnode.text)){
    nodeOps.setTextContent(elm,'');
    }
    //添加子节点
    addVnodes(elm,null,ch.length-1)
    }else if(isDef(oldCh)){
    //老的有子节点,新的没有
    removeVnodes(elm,oldCh.length-1)
    }else if(isDef(oldVnode.text)){
    //否则老的有文本内容 直接置空就行
    nodeOps.setTextContent(elm,'');
    }
    }else if(oldVnode.text !== vnode.text){
    //直接修改文本
    nodeOps.setTextContent(elm,vnode.text);
    }
    }

    function updateChildren(parentElm,newCh,removeOnly){
    //这里认真读下,没什么难度的,不行的话 也可以搜索下图文描述这段过程的

    let oldStartIdx = 0;
    let newStartIdx =0;
    let oldEndIdx = oldCh.length -1;
    let oldStartVnode = oldCh[0];
    let oldEndVnode = oldCh[oldEndIdx];
    let newEndIdx = newCh.length-1;
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let refElm;
    const canMove = !removeOnly
    while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx){
    if(isUndef(oldStartVnode)){
    oldStartVnode = oldCh[++oldStartIdx]
    }else if(isUndef(oldEndVnode)){
    oldEndVnode = oldCh[--oldEndIdx]
    }else if(sameVnode(oldStartVnode,newStartVnode)){
    patchVnode(oldStartVnode,newStartVnode)
    oldStartVnode = oldCh[++oldStartIdx]
    newStartVnode = newCh[++newStartIdx]
    }else if(sameVnode(oldEndVnode,newEndVnode)){
    patchVnode(oldEndVnode,newEndVnode)
    oldEndVnode = oldCh[--oldEndIdx];
    newEndVnode = newCh[--newEndIdx];
    }else if(sameVnode(oldStartVnode,newEndVnode)){
    patchVnode(oldStartVnode,newEndVnode);
    //更换顺序
    canMove && nodeOps.insertBefore(parentElm,oldStartVnode.elm,nodeOps.nextSibling(oldEndVnode.elm))
    oldStartVnode = oldCh[++oldStartIdx]
    newEndVnode = newCh[--newEndIdx]
    }else if(sameVnode(oldEndVnode,newStartVnode)){
    patchVnode(oldEndVnode,newStartVnode)
    canMove && nodeOps.insertBefore(parentElm,oldEndVnode.elm,oldStartVnode.elm)
    oldEndVnode = oldCh[--oldEndIdx]
    newStartVnode = newCh[++newStartIdx]
    }else{
    createElm(newStartVnode,oldStartVnode.elm)
    newStartVnode = newCh[++newStartIdx];
    }
    }

    if(oldStartIdx > oldEndIdx){
    //老的提前相遇,添加新节点中没有比较的节点
    refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx+1].elm
    addVnodes(parentElm,refElm,newStartIdx,newEndIdx)
    }else{
    //新的提前相遇 删除多余的节点
    removeVnodes(parentElm,oldStartIdx,oldEndIdx)
    }
    }
    function removeVnodes(parentElm,vnodes,startIdx,endIdx){
    for(;startIdx<=endIdx;++startIdx){
    const ch = vnodes[startIdx];
    if(isDef(ch)){
    removeNode(ch.elm)
    }
    }
    }

    function addVnodes(parentElm,endIdx){
    for(;startIdx <=endIdx;++startIdx ){
    createElm(vnodes[startIdx],refElm)
    }
    }

    function sameVnode(vnode1,vnode2){
    return vnode1.tag === vnode2.tag
    }
    function removeNode(el){
    const parent = nodeOps.parentNode(el)
    if(parent){
    nodeOps.removeChild(parent,el)
    }
    }
    function removeVnodes(parentElm,endIdx){
    for(;startIdx<=endIdx;++startIdx){
    const ch = vnodes[startIdx]
    if(isDef(ch)){
    removeNode(ch.elm)
    }
    }
    }
    function isDef (s){
    return s != null
    }
    function isUndef(s){
    return s == null
    }
    function createChildren(vnode,children){
    if(Array.isArray(children)){
    for(let i=0;i<children.length;i++){
    createElm(children[i],vnode.elm,null)
    }
    }
    }
    function createElm(vnode,refElm){
    const children = vnode.children
    const tag = vnode.tag
    if(isDef(tag)){
    // 非文本节点
    vnode.elm = nodeOps.createElement(tag); // 其实可以初始化的时候就赋予
    createChildren(vnode,children);
    insert(parentElm,refElm)
    }else{
    vnode.elm = nodeOps.createTextNode(vnode.text)
    insert(parentElm,refElm)
    }
    }
    function insert(parent,elm,ref){
    if(parent){
    if(ref){
    nodeOps.insertBefore(parent,ref)
    }else{
    nodeOps.appendChild(parent,elm)
    }
    }
    }

    这就是完整实现了

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程之家。

    猜你在找的Vue相关文章