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

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

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

若是你以为排版难看,请点击 下面连接 或者 拉到 下面关注公众号也能够吧数组

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

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

原本能够放到 Diff 那里写,可是所有合起来内容又太多dom

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

因此打算独立一篇文章,先预热一下,内容也很少,也挺简单,光看下也会对咱们的思惟有所帮助工具


节点操做函数

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

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

下面会介绍三个函数url

insert,createElm,createChildren

简单介绍

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

createElm 做用是建立DOM 节点

createChildren 做用也是建立DOM 节点,可是处理的是一个数组,而且会建立 DOM 节点 和 文本节点

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

1 insert

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

可是插入也会分两种状况

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

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

能够说这个函数是 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 文本节点

当 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

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

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

到时不会仔细介绍


最后

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

公众号
相关文章
相关标签/搜索