VUE DIFF算法之简单DIFF

VUE DIFF算法系列讲解

VUE 双端DIFF算法
VUE 快速DIFF算法



前言

最近的几次面试中,发现不少中高级前端工程师,都会写到了解vue底层设计原理,那既然人写都写了,还不得问一下子,但是看大家的回答效果,建议还是不要写原理相关内容了。
在最近的几场面试中,大家对这个问题的回答其实都是答的双端diff算法,其实如果你真的不太熟悉双端diff,面试官又问了,那还不如给出一个自己的diff算法,起码做到先能用,再优化,别尬在现场。本文中提到的算法,个人感觉大部分人应该还是比较好理解的,也能较快的转化成自己的内容。下面我们就通过代码实现及实际案例,详细了解一下


一、简单DIFF算法的代码实现

首先说一下diff主要是干什么?diff其实就是通过新旧vdom的对比,通过移动,新增和删除,在保证olddom能被最大复用的情况下,将旧的虚拟dom,变成新的虚拟dom的样子。
下边我们展示下实现的代码,代码都有详细的注释,大家如果看到觉得有问题或者不太理解的地方,希望可以帮忙反馈在评论区。

提示:不需要特别关注patch等函数的实现,和diff的关系不是很大。理解其大致想做的事即可,具体模拟实现可参考文末github链接

// n1 旧的vdom  n2 新的vdom组
const oldChildren = n1.children;
const newChildren = n2.children;

const oldLen = oldChildren.length;
const newLen = newChildren.length;

// 加key的情况下
// 用来存储在寻找过程中,遇到的最大索引值(oldChildren中的索引)
let lastIndex = 0;
for (let i = 0; i < newLen; i++) {
    
    
  const newVnode = newChildren[i];

  // 定义一个变量。来存储当前的newVnode是否在旧的节点中被找到
  let find = false;
  for (let j = 0; j < oldLen; j++) {
    
    
    const oldVnode = oldChildren[j];
    if (newVnode.key === oldVnode.key) {
    
    
      patch(oldVnode, newVnode, container);
      if (j < lastIndex) {
    
    
        // 如果当前找到的节点在oldchildren中的索引值小于最大索引值 lastIndex,
        // 说明该节点需要进行移动
        const preVnode = newChildren[i - 1];

        // 如果preVnode不存在,说明他是第一个位置,不需要移动
        if (preVnode) {
    
    
          // 由于我们要将newVnode对应的真实DOM节点移动到preVnode对应的真实DOM后
          // 所以我们获取newVnode对应的真实DOM节点的下一个节点作为锚点
          const anchor = preVnode.el.nextSibling;
          insert(newVnode.el, container, anchor);
        }
      } else {
    
    
        lastIndex = j;
      }
      break;
    }
  }

  // 如果当前newVnode没有被找到,也就说明此节点为新增节点
  if (!find) {
    
    
    const preVnode = newChildren[i - 1];
    let anchor = null;
    if (preVnode) {
    
    
      // 如果当前节点有一个node节点,则使用它的下一个兄弟节点作为锚点
      anchor = preVnode.el.nextSibling;
    } else {
    
    
      // 如果没有,则使用container的第一个节点作为锚点
      anchor = container.firstChild;
    }
    // 如果anchor没找到,则 newNode 将被插入到子节点的末尾
    patch(null, newVnode, container, anchor);
  }
}

// 完成上一步操作后,遍历一下旧的节点,看哪些需要卸载
for (let i = 0; i < oldChildren.length; i++) {
    
    
  // 拿旧的节点在新的节点中找
  const has = newChildren.find(vnode => vnode.key === oldChildren[i]);

  if (!has) {
    
    
    unmount(oldChildren[i]);
  }
}

二、实践

1. 练习1

// oldChildrem
p-1 p-2 p-3

// newChildren
p-3 p-1 p-4 p-2

初始情况下,lastIndex = 0
对newChildren和oldChildrem进行嵌套循环,
i = 0     new    old    find
  j = 0:  p-3    p-1    false
  j = 1:  p-3    p-2    false
  j = 2:  p-3    p-3    true
此时,j >= lastIndex, 所以将lastIndex更新为2, 位置不需要移动; 找到对应节点后,break掉本次循环。此时节点情况如下:
p-1 p-2 p-3

i = 1     new    old    find
  j = 0:  p-1    p-1    true
此时,j < lastIndex, 位置需要移动,锚点为newChildren[i - 1],所以,锚点为p-3,将其插在p-3后边。找到对应节点后,break掉本次循环。此时节点情况如下:
p-2 p-3 p-1

i = 2     new    old    find
  j = 0:  p-4    p-1    false
  j = 1:  p-4    p-2    false
  j = 2:  p-4    p-3    false
因为本次循环中,new vnode在 oldChildren中没有找到,所以需要对此节点进行挂载。挂载锚点为newChildren[i - 1]即p-1,所以把此节点插在p-1之后,此时节点情况如下:
p-2 p-3 p-1 p-4

i = 3     new    old    find
  j = 0:  p-2    p-1    false
  j = 1:  p-2    p-2    true
此时,j < lastIndex, 位置需要移动,锚点为newChildren[i - 1],所以,锚点为p-4,将其插在p-4后边。找到对应节点后,break掉本次循环。此时节点情况如下:
p-3 p-1 p-4 p-2

循环结束后,需要单独循环oldChildren, 看哪些oldVnode在newChildren中没有,此demo中,oldVnode均可在newChildren找到,所以不做任何操作

2. 练习2

// oldChildrem
p-1 p-2 p-3

// newChildren
p-4 p-2

初始情况下,lastIndex = 0
对newChildren和oldChildrem进行嵌套循环,
i = 0     new    old    find
  j = 0:  p-4    p-1    false
  j = 1:  p-4    p-2    false
  j = 2:  p-4    p-3    false
因为本次循环中,new vnode在 oldChildren中没有找到,所以需要对此节点进行挂载。挂载锚点为newChildren[i - 1]undefined,因前边无节点,所以将锚点设置为此container的第一个元素,将其放在其对前边,此时顺序为
p-4 p-1 p-2 p-3

i = 1     new    old    find
  j = 0:  p-2    p-1    false
  j = 1:  p-2    p-2    true
此时,j <= lastIndex, 位置不需要移动,此时顺序为
p-4 p-1 p-2 p-3

循环结束后,需要单独循环oldChildren, 看哪些oldVnode在newChildren中没有,此demo中,p-1和p-3均无法在newChildren中找到,将其卸载此时顺序为
p-4 p-2

总结

本文主要讲解了简单diff的代码实现,并通过两个例子进行了深入的理解,因为此算法比较简单,所以应该更容易转化成为我们自己的东西。在面试过程中,如果真的记不起双端算法,也可以回答下简单diff算法。

参考:<<vue设计与实现>>第9章
github:link

猜你喜欢

转载自blog.csdn.net/qq_39544148/article/details/128857943