【Vue原理】Diff - 源码版 之 相关辅助函数

写文章不容易,点个赞呗兄弟


专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue版本 【2.5.17】

如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧

【Vue原理】Diff - 源码版 之 相关辅助函数

在开始我们的 Diff 主要内容之前,我们先来了解下其中会用的的一些辅助函数

本来可以放到 Diff 那里写,但是全部合起来内容又太多

而且这些函数比较具有公用性,就是抽出来用

所以打算独立一篇文章,先预热一下,内容也不多,也挺简单,光看下也会对我们的思维有所帮助


节点操作函数

下面是 Diff 在比较节点时,更新DOM 会使用到的一些函数

本来会有更多,为了看得方便,我把一些合并了

下面会介绍三个函数

insert,createElm,createChildren

简单介绍

insert 作用是把节点插入到某个位置

createElm 作用是创建DOM 节点

createChildren 作用也是创建DOM 节点,但是处理的是一个数组,并且会创建 DOM 节点 和 文本节点

下面就来仔细说说这三个方法

1 insert

这个函数的作用就是 插入节点

但是插入也会分两种情况

1、没有参考兄弟节点,直接插入父节点的子节点末尾

2、有参考兄弟节点,则插在兄弟节点前面

可以说这个函数是 Diff 的基石哈哈

function insert(parent, elm, ref) {    

    if (parent) {        

        if (ref) {            

            if (ref.parentNode === parent) {

                parent.insertBefore(elm, ref);
            }
        } else {
            parent.appendChild(elm);
        }
    }
}

2 createElm

这个函数的作用跟它的名字一样,就是创建节点的意思

创建完节点之后,会调用 insert 去插入节点

你可以看一下,不难

function createElm(vnode, parentElm, refElm) {  



    var children = vnode.children;    

    var tag = vnode.tag;    

    if (tag) {

        vnode.elm = document.createElement(tag)        



        // 先把 子节点插入 vnode.elm,然后再把 vnode.elm 插入parent

        createChildren(vnode, children);

       

        //  插入DOM 节点

        insert(parentElm, vnode.elm, refElm);
    }    



    else {

        vnode.elm = document.createTextNode(vnode.text);
        
        insert(parentElm, vnode.elm, refElm);
    }
}

createElm 需要根据 Vnode 来判断需要创建什么节点

1、文本节点

2、普通节点

1 文本节点

当 vnode.tag 为 undefined 的时候,创建文本节点,看下 真实文本vnode

公众号

并且,文本节点是没有子节点的

2 普通节点

vnode.tag 有值,那就创建相应的 DOM

但是 该 DOM 可能存在子节点,所以子节点甚至子孙节点,也都是要创建的

所以会调用一个 createChildren 去完成所有子孙节点的创建

3 createChildren

这个方法处理子节点,必然是用遍历递归的方法逐个处理的

1如果子节点是数组,则遍历执行 createElm 逐个处理

2如果子节点的 text 属性有数据,则表示这个 vnode 是个文本节点,直接创建文本节点,然后插入到父节点中

function createChildren(vnode, children) {    



    if (Array.isArray(children)) {      



        for (var i = 0; i < children.length; ++i) {

            createElm(children[i], vnode.elm, null);
        }



    }



    else if (        

        typeof vnode.text=== 'string' ||

        typeof vnode.text=== 'number' ||
        typeof vnode.text=== 'boolean'
    ) {
        vnode.elm.appendChild(

            document.createTextNode(vnode.text)

        )

    }
}

服务Diff工具函数

下面的函数是 Vue 专门用来服务 Diff 的,介绍两个

createKeyToOldIdx,sameVnode

1createKeyToOldIdx

接收一个 children 数组,生成 key 与 index 索引对应的一个 map 表

function createKeyToOldIdx(

    children, beginIdx, endIdx

) {    



    var i, key;    

    var map = {};    



    for (i = beginIdx; i <= endIdx; ++i) {

        key = children[i].key;        

        if (key) {

            map[key] = i;
        }
    }    

    return map

}

比如你的旧子节点数组是

[{    
    tag:"div",  key: "key_1"

},{  

    tag:"strong", key:"key_2"

},{  

    tag:"span",  key:"key_4"

}]

经过 createKeyToOldIdx 生成一个 map 表 oldKeyToIdx,是下面这样

{
    "key_1":0,
    "key_2":1,
    "key_4":2
}

把 vnode 的 key 作为属性名,而该 vnode 在children 的位置 作为 属性值

这个函数在 Diff 中的作用是

判断某个新 vnode 是否在 这个旧的 Vnode 数组中,并且拿到它的位置。就是拿到 新 Vnode 的 key,然后去这个 map 表中去匹配,是否有相应的节点,有的话,就返回这个节点的位置

比如

现在我有一个 新子节点数组,一个 旧子节点数组

我拿到 新子节点数组 中的某一个 newVnode,我要判断他是否 和 旧子节点数组 中某个vnode相同

要怎么判断???难道是双重遍历数组,逐个判断 newVnode.key==vnode.key ??

Vue 用了更聪明的办法,使用 旧 Vnode 数组生成一个 map 对象 obj

当 obj[ newVnode.key ] 存在的时候,说明 新旧子节点数组都存在这个节点

并且我能拿到该节点在 旧子节点数组 中的位置(属性值)

反之,则不存在

这个方法也给我们提供了在项目中相似场景的一个解决思路,以对象索引查找替代数组遍历

希望大家记住哦

2 sameVnode

这个函数在 Diff 中也起到非常大的作用,大家务必要记住啊

它的作用是判断两个节点是否相同

这里说的相同,并不是完全一毛一样,而是关键属性一样,可以先看下源码

function sameVnode(a, b) {    



    return (

        a.key === b.key &&
        a.tag === b.tag &&
        !!a.data === !!b.data &&
        sameInputType(a, b)
    )
}



function sameInputType(a, b) {    



    if (a.tag !== 'input') return true

    var i;    

    var types = [

        'text','number','password',

        'search','email','tel','url'

    ]    



    var typeA = (i = a.data) && (i = i.attrs) && i.type;    

    var typeB = (i = b.data) && (i = i.attrs) && i.type;    

    

    // input 的类型一样,或者都属于基本input类型

    return (
        typeA === typeB ||
        types.indexOf(typeA)>-1 &&

        types.indexOf(typeB)>-1

    )
}

判断的依据主要是 三点,key,tag,是否存在 data

这里判断的节点是只是相对于 节点本身,并不包括 children 在内

也就是说,就算data不一样,children 不一样,两个节点还是可能一样

比如下面这两个

公众号

公众号

有一种特殊情况,就是 input 节点

input 需要额外判断, 两个节点的 type 是否相同

或者

两个节点的类型可以不同,但是必须属于那些 input 类型

sameVnode 的内容就到这里了,但是我不禁又开始思考一个问题

为什么 sameVnode 会这么判断??

下面纯属个人意淫想法,仅供参考

sameVnode 应用在 Diff ,作用是为了判断节点是否需要新建

当两个 新旧vnode 进行 sameVnode 得到 false 的时候,说明两个vnode 不一样,会新建DOM插入

也就是两个节点从根本上不一样时才会创建

其中会比较 唯一标识符 key 和 标签名 tag,从而得到 vnode 是否一样 ,这些是毫无疑问的了

但是这里不需要判断 data 是否一样,我开始不太明白

后面想到 data 是包含有一些 dom 上的属性的,所以 data 不一样没有关系

因为就算不一样,他们还是基于同一个 DOM

因为DOM属性的值是可能是动态绑定动态更新变化的,所以变化前后的 两个 vnode,相应的 data 肯定不一样,但是其实他们是同一个 Vnode,所以 data 不在判断范畴

但是 data 在新旧节点中,必须都定义,或者都不定义

不存在 一个定义,而一个没定义, 但是会相同的 Vnode

比如,下面这个就会存在data

公众号

这个就不会存在data

公众号

他们在模板中,肯定是不属于同一个节点


总结

涉及的函数主要分为两类

一类是专门负责操作 DOM 的,insert,createElm,createChildren

这类函数比较通用,就算在我们自己的项目中也可以用得上

一类是专门特殊服务 Diff 的,createKeyToOldIdx,sameVnode

其中会包含一些项目的解决思路

大家务必先记住一下这几个函数,在下篇内容的源码中会频繁出现

到时不会仔细介绍


最后

鉴于本人能力有限,难免会有疏漏错误的地方,请大家多多包涵,如果有任何描述不当的地方,欢迎后台联系本人,有重谢

公众号

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
vue-code-diff是一个用于在Vue项目中显示代码差异的插件。当vue-code-diff不对齐时,可能有以下几个原因和解决方法: 1. 插件本不兼容:首先要确保vue-code-diff插件的本与Vue项目的本兼容。可以通过查看vue-code-diff的文档或者安装最新本的插件来解决这个问题。 2. 样式冲突:vue-code-diff的展示需要一定的样式支持,可能存在与项目中其他样式库或自定义样式发生冲突的情况。可以通过使用scoped样式、更改样式命名空间或重新设计样式来解决这个问题。 3. 代码格式问题:如果代码在Vue组件中使用了不同的缩进或对齐方式,vue-code-diff可能会出现不对齐的情况。在这种情况下,需统一代码的缩进和对齐方式,确保比对的代码结构一致。 4. 数据问题:vue-code-diff的对比结果是基于提供的数据进行展示的。如果数据不正确或者解析出现问题,将导致代码不对齐。检查数据的正确性,并确保数据用于生成代码差异的函数正确返回对应的数据。 5. 其他问题:如果以上方法都没有解决vue-code-diff不对齐的问题,那可能是插件本身的bug或者其他未知的原因。可以尝试在插件的GitHub页面上提交issue,或者尝试寻找其他的代码对比插件替代解决。 总之,对于vue-code-diff不对齐的问题,需要根据具体情况进行分析和排查,找出具体原因并采取相应的解决方法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值