资讯专栏INFORMATION COLUMN

Snabbdom.js(三)

dantezhao / 645人阅读

摘要:总共写了四篇文章都是自己的一些拙见,仅供参考,请多多指教,我这边也会持续修正加更新介绍一下基本用法介绍一下渲染原理介绍一下的算法和对值的认识介绍一下对于兼容的修改这篇主要是说一下的算法在上一篇中我总结过对比渲染的流程大体分为通过来判断两个是

总共写了四篇文章(都是自己的一些拙见,仅供参考,请多多指教,我这边也会持续修正加更新)

介绍一下snabbdom基本用法

介绍一下snabbdom渲染原理

介绍一下snabddom的diff算法和对key值的认识

介绍一下对于兼容IE8的修改

这篇主要是说一下snabbdom的diff算法

在上一篇中我总结过:
对比渲染的流程大体分为
1.通过sameVnode来判断两个vnode是否值得进行比较
2.如果不值得,直接删除旧的vnode,渲染新的vnode
3.如果值得,调用模块钩子函数,对其节点的属性进行替换,例如style,event等;再判断节点子节点是否为文本节点,如果为文本节点则进行更替,如果还存在其他子节点则调用updateChildren,对子节点进行更新,更新流程将会回到第一步,重复;

这篇文章的重点就是说一下updateChildren这个函数

sameVnode
function sameVnode(vnode1, vnode2) {
    return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel;
}

这是一个比较两个vnode是否相似,是否值得去进行比较的函数,那么这里为什么会提到它?因为这里面有一个很重要的值---key
在平时的使用中几乎用不到这个key值,不会去专门给它一个定义值,因为undefined===undefined,不会影响其比较;

key的作用

key值的出现主要是为了应付一些场景
例如:

    • 1
    • 2
    • 2
    • -->
    • 3
    • 3
    • 4

对于这种情况,如果按照正常的做法,就是一个个vnode去进行比较,发现其文本节点不对,就会一个个进行替换例如:

  • 1
  • -->
  • 2
  • .... 这样就会进行三次dom操作

    对于这种情况是否可以优化呢?
    答案是可以的,我们可以删除

  • 1
  • ,然后添加一个
  • 4
  • ,这样就只进行了两次dom操作就完成了需要的效果

    那这里就涉及到一个标记值,标记着在新vnode中还有哪些旧的vnode存在,key值就是充当着这个角色。

    [1(key:a),2(key:b),3(key:c)]

    [2(key:b),3(key:c),4(key:d)]

    [a,b,c] -> [a(x),b,c,d(+)] === [1,2,3] --> [1(x),2,3,4(+)]

    key值与vnode形成了一个映射,可以看到,我们通过对key值的排序、增删间接完成了对vnode的操作,使用最少的dom操作来完成了

    如何对key值进行排序,增删

    那这里就会有一个问题,我们如何完成上面的操作呢?这个过程我们可以理解为一种优化对比渲染的过程,也就是diff算法的核心

    建议大家先看一下这一篇文章,图文并茂

    我这边举一个复杂的例子,记录每一步的操作:

    下面是页面真实的dom,分别保存在自己vnode的elm属性上;旧-->新

      • a
      • a
      • b
      • d
      • c
      • f
      • f
      • h
      • e
      • -->
      • k
      • d
      • b
      • g
      • g

    假设每个元素都有一个key值一一对应,且不重复,它们的key值分别为

        a:1            a:1
        b:2            d:6
        c:3            f:4
        f:4    -->     h:8
        e:5            k:9
        d:6            b:2
        g:7            g:7

    将旧新vnode分别放入两个数组

    old:[vnode,....] 
    new:[vnode,....]
    

    其实我们是比较其key值是否相等,然后再决定如何排序,增删vnode的位置,patch vnode,最终达到改变dom的目的,为了方便理解,我这里把其key值拿出来放入一个数组,每一个key在数组中的索引都对应着相应的vnode在其数组中的索引,在真实代码中是直接比较vnode.key值。

    oldKey:[1,2,3,4,5,6,7] 
    
    oldStartIdx:0
    oldStartVal:1
    
    oldEndIdx:6
    oldEndVal:7
    
    newKey:[1,6,4,8,9,2,7]
    
    newStartIdx:0
    newStartVal:1
    
    newEndIdx:6
    newEndVal:7
    

    用的是双指针的方法,头尾同时开始扫描;
    循环两个数组,循环条件为(old_startIndex <= old_endIndex && new_startIndex <= new_endIndex)

    (下面说的patch是直接对vnoe.elm进行修改,调用前面的patchVnode函数,也就是直接对页面的dom进行修改,及时比较及时修改)

    比较oldStartVal和newStartVal是否相等,如果相等则oldStartIdx和newStartIdx分别加1,并对oldStartVal对应的vnode进行patch,进入下一次循环;这个例子中oldStartVal==newStartVal,所以oldStartIdx:1 newStartIdx:1;若不相等,继续比较;

     比较过后:
       oldStartIdx:1              oldEndIdx:6
       oldStartVal:2              oldEndVal:7
    
    
       newStartIdx:1              newEndIdx:6     
       newStartVal:6              newEndVal:7
    
    
    比较范围缩小后:
       
       oldKey:[2,3,4,5,6,7] 
       newKey:[6,4,8,9,2,7]
       
    dom:
       
  • a
  • b
  • c
  • f
  • e
  • d
  • g
  • oldVnodeArray:旧的vnode数组 [a,b,c,f,e,d,g]

    比较oldEndVal和newEndVal是否相等,如果相等则oldEndIdx和newEndIdx分别减1,并对oldEndVal对应的旧vnode进行patch,进入下一次循环;这里oldEndVal==newEndVal,所以oldEndIdx:5 newEndIdx:5;若不相等,继续比较;

    比较过后:
       oldStartIdx:1              oldEndIdx:5
       oldStartVal:2              oldEndVal:6
    
    
       newStartIdx:1              newEndIdx:5     
       newStartVal:6              newEndVal:2
    
    比较范围缩小后:
       
       oldKey:[2,3,4,5,6] 
       newKey:[6,4,8,9,2]
    
    dom:
       
  • a
  • b
  • c
  • f
  • e
  • d
  • g
  • oldVnodeArray:旧的vnode数组 [a,b,c,f,e,d,g]

    比较oldStartVal和newEndVal是否相等,如果相等则oldStartIdx和newEndIdx分别加1和减1,oldStartVal对应的vnode移动到oldEndVal对应的vnode后面,并对移动的vnode进行patch,进入下一次循环;这里oldStartVal==newEndVal,所以oldStartIdx:2 newEndIdx:4;若不相等,继续比较;

    比较过后:
       oldStartIdx:2              oldEndIdx:5
       oldStartVal:3              oldEndVal:6
    
    
       newStartIdx:1              newEndIdx:4     
       newStartVal:6              newEndVal:9
    
    比较范围缩小后:
       
       oldKey:[3,4,5,6] 
       newKey:[6,4,8,9]
    dom:
       
  • a
  • c
  • f
  • e
  • d
  • b
  • g
  • oldVnodeArray:旧的vnode数组 [a,b,c,f,e,d,g]

    比较oldEndVal和newStartVal是否相等,如果相等则oldEndIdx和newStartIdx分别减1和加1,oldEndVal对应的vnode移动到oldStart对应的vnode前面,并对移动的vnode进行patch,进入下一次循环;这里oldEndVal==newStartVal,所以oldEndIdx:4 newStartIdx:2;若不相等,继续比较;

    比较过后:
       oldStartIdx:2              oldEndIdx:4
       oldStartVal:3              oldEndVal:5
    
    
       newStartIdx:2              newEndIdx:4     
       newStartVal:4              newEndVal:9
    
    比较范围缩小后:
       
       oldKey:[3,4,5] 
       newKey:[4,8,9]
       
    dom:
       
  • a
  • d
  • c
  • f
  • e
  • b
  • g
  • oldVnodeArray:旧的vnode数组 [a,b,c,f,e,d,g]

    若不满足上述判断条件,查找newStartVal对应的vnode是否存在于旧vnode数组中。若存在,移动这个旧的vnode到oldStartVal对应的vnode前面,并对这个移动的vnode进行patch,在旧的vnode数组中将其原来的位置置为undefined,并且newStartIdx加1;

    比较过后:
       oldStartIdx:2              oldEndIdx:4
       oldStartVal:3              oldEndVal:5
    
    
       newStartIdx:3              newEndIdx:4     
       newStartVal:8              newEndVal:9
       
    
    比较范围缩小后:
       
       oldKey:[3,4,5] 
       newKey:[8,9]
       
    dom:
       
  • a
  • d
  • f
  • c
  • e
  • b
  • g
  • oldVnodeArray:旧的vnode数组 [a,b,c,undefined,e,d,g]

    若不存在,则将这个newStartVal对应的vnde添加到oldStartVal对应的vnode前面,并且newStartIdx加1;

     比较过后:
       oldStartIdx:2              oldEndIdx:4
       oldStartVal:3              oldEndVal:5
    
    
       newStartIdx:4              newEndIdx:4     
       newStartVal:9              newEndVal:9
    
    比较范围缩小后:
       
       oldKey:[3,4,5] 
       newKey:[9]
       
    dom:
       
  • a
  • d
  • f
  • h
  • c
  • e
  • b
  • g
  • oldVnodeArray:旧的vnode数组 [a,b,c,undefined,e,d,g] 这里循环了两次 比较过后: oldStartIdx:2 oldEndIdx:4 oldStartVal:3 oldEndVal:5 newStartIdx:5 newEndIdx:4 newStartVal:undefined newEndVal:9 比较范围缩小后: oldKey:[3,4,5] newKey:[] dom:
  • a
  • d
  • f
  • h
  • k
  • c
  • e
  • b
  • g
  • oldVnodeArray:旧的vnode数组 [a,b,c,undefined,e,d,g]

    循环结束,判断新旧vnode的key值哪个遍历完,如果旧的便利完,若旧vnode数组遍历完,则将剩余的新vnode数组中的vnode进行添加;若新vnode数组遍历完,则删除剩余的旧vnode数组中的vnode
    在上面例子中,我们需要删除oldVnodeArray中的三个vnode,索引分别为3,4,5,从而删除了vnode对应的elm
    最后得到最终的dom结构

               
    • a
    • d
    • f
    • h
    • k
    • b
    • g

    上面的例子没有将所有情况全部归纳进来,不过应该包含了大部分情况了。还需要注意的就是:

    上面只是提到了key值,其实比较两个vnode是否相似还有一个sel属性,必须要两个都相等才行

    正常情况下key值用到的地方也是ul-li tr-td这种子元素重复的场景,因为这种情况下才会涉及到子元素顺序改变还能复用

    通过上面的分析,其实还可以发现一个key值的特点,就是唯一性和一一对应性。唯一性好理解,毕竟key值就是用来每个vnode自己的标示;一一对应代表着是你旧vnode和新vnode中如果没有改变,则其key值应保持不变,之所以要提这个是因为很多地方看到了进行循环渲染的时候其key值都是用的数组的index进行赋值

    如果考虑这种情况
    
  • a
  • c
  • b
  • -->
  • a
  • c
  • b
  • 一般这种dom结构都是放在数组里面循环输出的,如果它们的key值是按照index进行赋值的话,就需要这个地方需要进行三次dom操作,就是依次修改其节点的文本值; [a(1),b(2),c(3)] [c(1),a(2),b(3)] 那如果我使得它们改变前面对应的元素的key值不改变的,一一对应的话,这里只需要一次dom操作,就是把
  • c
  • 移动到最前面 [a(1),b(2),c(3)] [c(3),a(1),b(2)]

    文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

    转载请注明本文地址:https://www.ucloud.cn/yun/100371.html

    相关文章

    • React && VUE Virtual Dom的Diff算法统一之路 snabbd

      摘要:毫无疑问的是算法的复杂度与效率是决定能够带来性能提升效果的关键因素。速度略有损失,但可读性大大提高。因此目前的主流算法趋向一致,在主要思路上,与的方式基本相同。在里面实现了的算法与支持。是唯一添加的方法所以只发生在中。 VirtualDOM是react在组件化开发场景下,针对DOM重排重绘性能瓶颈作出的重要优化方案,而他最具价值的核心功能是如何识别并保存新旧节点数据结构之间差异的方法,...

      shixinzhang 评论0 收藏0
    • Snabbdom.js(一)

      摘要:闲聊在学的过程中,虚拟应该是听的最多的概念之一,得知其是借鉴进行开发,故习之。以我的观点来看,多个相同元素渲染时,则需要为每个元素添加值。 闲聊:在学vue的过程中,虚拟dom应该是听的最多的概念之一,得知其是借鉴snabbdom.js进行开发,故习之。由于我工作处于IE8的环境,对ES6,TS这些知识的练习也只是浅尝辄止,而snabbdom.js从v.0.5.4这个版本后开始使用TS...

      mating 评论0 收藏0
    • snabbdom源码粗读

      摘要:这个大概是的钩子吧在每一次插入操作的时候都将节点这类型方法可以看出来是在调用对应的方法因为开始的时候就导入进来了插入节点操作的时候都需要加入子节点有子元素也就是的时候递归调用循环子节点生成对应着一些操作之后都要触发钩子函数。 snabbdom 本文的snabbdom源码分析采用的是0.54版本(即未用ts重写前的最后一版) 前期了解 snabbdom被用作vue的虚拟dom。本文的一个...

      svtter 评论0 收藏0
    • snabbdom.js(四)

      摘要:总共写了四篇文章都是自己的一些拙见,仅供参考,请多多指教,我这边也会持续修正加更新介绍一下基本用法介绍一下渲染原理介绍一下的算法和对值的认识介绍一下对于兼容的修改这篇主要是记录一下针对做了哪些修改增加用来兼容某些功能函数,例如等将每个文件单 总共写了四篇文章(都是自己的一些拙见,仅供参考,请多多指教,我这边也会持续修正加更新) 介绍一下snabbdom基本用法 介绍一下snabbdo...

      wuyangchun 评论0 收藏0
    • snabbdom.js(二)

      摘要:如果新旧的和都相同,说明两个相似,我们就可以保留旧的节点,再具体去比较其差异性,在旧的上进行打补丁否则直接替换节点。 总共写了四篇文章(都是自己的一些拙见,仅供参考,请多多指教,我这边也会持续修正加更新) 介绍一下snabbdom基本用法 介绍一下snabbdom渲染原理 介绍一下snabddom的diff算法和对key值的认识 介绍一下对于兼容IE8的修改 这篇我将以自己的思路去...

      浠ラ箍 评论0 收藏0

    发表评论

    0条评论

    最新活动
    阅读需要支付1元查看
    <