vue基础知识之-virtual dom 原理实现

73 篇文章 1 订阅
4 篇文章 1 订阅

virtual dom 原理实现

  • 创建dom树

  • 树的diff,同层对比,输出patches(listDiff/diffChilder/diffProps)

    • 没有新节点,返回
    • 新的节点tagName与key不变,对比props,继续递归遍历子树
      • 对比属性(对比新旧属性列表)
        • 旧属性是否存在与新属性列表中
        • 都存在的是否有变化
        • 是否出现旧列表中没有的新属性
    • tagName和key值变化了,则直接替换成新节点
  • 渲染差异

    • 遍历patches,把需要更改的节点取出来
    • 局部更新dom
function diff(oldTree,newTree){
    //差异收集
    let patches ={};
    dfs(oldTree,newTree,0,patches);
    return patches;
}

function dfs(oldNode,newNode,index,patches){
    let curPatches = [];
    if(newNode){
        //当前旧节点tagname和key值完全一致时
        if(oldNode.tagName === newNode.tagName&&oldNode.key===newNode.key){
            //继续比对属性
            let props= diffProps(oldNode.props,newNode.props);
            curPatches.push({type:'changeProps',props});
            //递归进入下一层级的比较
            diffChildrens(oldNode.children,newNode.children,index,patches)
        }else{
            //当tagName或者key修改后,表示已经是全新节点,无需再比
            curPathes.push({type:'replaceNode',node:newNode});
        }
    }
    //构建出整颗差异树

    if(curPatches.length){
        if(patches[index]){
            patches[index] = patches[index].contact(curPatches)
        }else{
            patches[index] = curPatches;
        }
    }
}

//属性对比实现
function diffProps(oldProps,newProps){
    let propsPatches = [];
    // 遍历新旧属性列表
    // 查找删除项
    // 查找修改项
    // 查找新增项
    forEach(oldProps,(k,v)=>{
        if(!newProps.hasOwnProperty(k)){
        propsPatches.push({type:'remove',prop:k});
        }else{
            if(v!==newProps[k]){
                propsPatches.push({type:'change',prop:key,value:newProps[k]});
            }
        }
    })
    forEach(newProps,(k,v)=>{
        if(!oldProps.hasOwnProperty){
            propsPatches.push({type:'add',prop:k,value:v});
        }
    })
    return propsPatches;
}

//对比子级差异
function diffChildrens(oldChild,newChild,index,patches){
    //标记子级的删除/新增/移动
    let {change,list} = diffList(oldChild,newChild,index,patches);
    if(change.length){
        if(patches[index]){
            patches[index]=patches[index].contact(change);
        }else{
            patches[index]=change;
        }
    }

    //根据key获取原本匹配的节点,进一步递归从头开始
    oldChild.map((item,i)=>{
        let keyIndex = list.indexOf(item.key);
        if(keyIndex){
            let node = newChild(keyIndex);
            //进一步递归比对
            dfs(item,node,index,patches);
        }
    })

}

// 列表对比,主要也是根据 key 值查找匹配项
// 对比出新旧列表的新增/删除/移动
function diffList(oldList,newList,index,patches){
    let change=[];
    let list = [];
    const newKeys = getKey(newList);
    oldList.map(v=>{
        if(newKeys.indexOf(v.key)>=-1){
            list.push(v.key);
        }else{
            list.push(null);
        }
    })

    //标记删除
    for(let i = list.length-1;i>=0;i--){
        if(!list[i]){
            list.splice(i,1);
            change.push({type:'remove',index:i});
        }
    }

    //标记新增和移动
    newList.map((item,i)=>{
        const key = item.key;
        const index = list.indexOf(key);
        if(index===-1||key==null){
            //新增
            change.push({type:'add',node:item,index:i});
            list.splice(i,0,key);
        }else{
            //移动
            if(index!==i){
                change.push({
                    type:'move',
                    from:index,
                    to,i,
                })
                move(list,index,i);
            }
        }
    })
    return {change,list}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值