Virtual DOM 就是用 JS 对象去 模拟 DOM 结构,它是真实 DOM 的抽象,只保留一些有用的信息,更轻量地描述 DOM 树的结构;新旧 vnode 对比,得出最小的更新范围,最后更新 DOM;数据驱动视图的模式下,有效控制 DOM 操作。

简单虚拟 DOM 结构

DOM 节点

  1. <div id="app" class="container">
  2. <p>我是p标签</p>
  3. <ul style="background-color: red">
  4. <li>我是a标签</li>
  5. </ul>
  6. </div>

js模拟 vnode 节点

  1. {
  2. tag: "div",
  3. props: {
  4. className: "container",
  5. id: "app",
  6. },
  7. children: [
  8. {
  9. tag: "p",
  10. children: "我是p标签",
  11. },
  12. {
  13. tag: "ul",
  14. props: { style: "background-color: red" },
  15. children: [
  16. {
  17. tag: "li",
  18. children: "我是a标签",
  19. },
  20. ],
  21. },
  22. ],
  23. };

snabbdom使用

案例代码:https://github.com/WuChenDi/Front-End/tree/master/12-snabbdom/virtual-dom

index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7. <title>snabbdom</title>
  8. </head>
  9. <body>
  10. <div id="app">app</div>
  11. <div id="container">container</div>
  12. <button id="btn-change">change</button>
  13. <script src="./src/index.js"></script>
  14. </body>
  15. </html>

index.js

  1. // index.js
  2. {
  3. import { init, h } from "snabbdom";
  4. let patch = init([]);
  5. let vnode = h("div", "Hello World");
  6. let app = document.querySelector("#app");
  7. console.log(app);
  8. let oldVnode = patch(app, vnode);
  9. vnode = h("div#dd", "Hello cdd");
  10. patch(oldVnode, vnode);
  11. }
  12. {
  13. import { init, h } from "snabbdom";
  14. let patch = init([]);
  15. let vnode = h("div#container", [
  16. h("h1", "我是h1标签"),
  17. h("ul", [h("li", "我是li标签")]),
  18. ]);
  19. let app = document.querySelector("#app");
  20. patch(app, vnode);
  21. setTimeout(() => {
  22. var newVnode = h("div#container", [h("h2", "我是h2标签")]);
  23. patch(vnode, newVnode);
  24. }, 2000);
  25. // setTimeout(() => {
  26. // var newVnode = h("div#container", [h("h2", "我是h2标签")]);
  27. // patch(vnode, h("!"));
  28. // }, 2000);
  29. }
  30. {
  31. import { init, h } from "snabbdom";
  32. // 定义 patch
  33. const patch = init([
  34. snabbdom_class,
  35. snabbdom_props,
  36. snabbdom_style,
  37. snabbdom_eventlisteners,
  38. ]);
  39. const container = document.getElementById("container");
  40. // 生成 vnode
  41. const vnode = h("ul#list", {}, [
  42. h("li.item", {}, "Item 1"),
  43. h("li.item", {}, "Item 2"),
  44. ]);
  45. patch(container, vnode);
  46. document.getElementById("btn-change").addEventListener("click", () => {
  47. // 生成 newVnode
  48. const newVnode = h("ul#list", {}, [
  49. h("li.item", {}, "Item 1"),
  50. h("li.item", {}, "Item B"),
  51. h("li.item", {}, "Item 3"),
  52. ]);
  53. patch(vnode, newVnode);
  54. });
  55. }

diff算法

步骤

用 js 对象来描述 dom 树结构,然后用这个 js 对象来创建一棵真正的 dom 树,插入到文档中;当状态更新时,将新的 js 对象和旧的 js 对象进行比较,得到两个对象之间的差异;最后将差异应用到真正的 dom 上。

复杂度

树 diff 的时间复杂度 O(n^3)

第一次需要对 oldVnode 遍历一次tree
然后第二次对 vnode 遍历tree
最后两者排序

优化时间复杂度到 O(n)

只比较同一层级,不跨级比较
tag 不相同,则直接删掉重建,不再深度比较
tag 与 key,两者都相同,则认为是相同节点,不再深度比较

虚拟DOM(Virtual DOM) 和 diff - 图1
虚拟DOM(Virtual DOM) 和 diff - 图2

snabbdom源码分析

https://github.com/snabbdom/snabbdom https://github.com/coconilu/Blog/issues/152

案例分析

  1. import {
  2. init,
  3. classModule,
  4. propsModule,
  5. styleModule,
  6. eventListenersModule,
  7. h,
  8. } from "snabbdom";
  9. const patch = init([
  10. // Init patch function with chosen modules
  11. classModule, // makes it easy to toggle classes
  12. propsModule, // for setting properties on DOM elements
  13. styleModule, // handles styling on elements with support for animations
  14. eventListenersModule, // attaches event listeners
  15. ]);
  16. const container = document.getElementById("container");
  17. const vnode = h("div#container.two.classes", { on: { click: someFn } }, [
  18. h("span", { style: { fontWeight: "bold" } }, "This is bold"),
  19. " and this is just normal text",
  20. h("a", { props: { href: "/foo" } }, "I'll take you places!"),
  21. ]);
  22. // Patch into empty DOM element – this modifies the DOM as a side effect
  23. patch(container, vnode);
  24. const newVnode = h(
  25. "div#container.two.classes",
  26. { on: { click: anotherEventHandler } },
  27. [
  28. h(
  29. "span",
  30. { style: { fontWeight: "normal", fontStyle: "italic" } },
  31. "This is now italic type"
  32. ),
  33. " and this is still just normal text",
  34. h("a", { props: { href: "/bar" } }, "I'll take you places!"),
  35. ]
  36. );
  37. // Second `patch` invocation
  38. patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state

大白话解释

首先 snabbdom 模块提供一个 init 方法,调用 init 方法会返回一个 patch 函数,第一个参数 oldVNode 或 DOM,第二个参数是新的 VNode 节点,调用 patch 函数会对 DOM 进行更新。通过调用 h 函数来创建 VNode。初始化 patch(container, vnode),container作为应用根节点,更新patch(vnode, newVnode)。

hook 钩子

Name Triggered when 触发时机 Arguments to callback
pre the patch process begins patch 开始之前 none
init a vnode has been added 已经创建了一个 vnode vnode
create a DOM element has been created based on a vnode 已经基于 vnode 创建了一个 DOM,但尚未挂载 emptyVnode, vnode
insert an element has been inserted into the DOM 创建的 DOM 被挂载了 vnode
prepatch an element is about to be patched 一个元素即将被 patch oldVnode, vnode
update an element is being updated 元素正在被更新 oldVnode, vnode
postpatch an element has been patched 元素已经 patch 完毕 oldVnode, vnode
destroy an element is directly or indirectly being removed 一个元素被直接或间接地移除了。间接移除的情况是指被移除元素的子元素 vnode
remove an element is directly being removed from the DOM 一个元素被直接移除了(卸载) vnode, removeCallback
post the patch process is done patch 结束 none

源码分析

https://github.com/WuChenDi/Front-End/blob/master/12-snabbdom/3.0.1

vnode

  1. export interface VNode {
  2. sel: string | undefined; // selector
  3. data: VNodeData | undefined;
  4. children: Array<VNode | string> | undefined; // 子节点
  5. elm: Node | undefined; // element, 存储 HTMLELement
  6. text: string | undefined; // 文本节点
  7. key: Key | undefined; // 节点 key
  8. }
  9. export interface VNodeData {
  10. props?: Props; // 节点属性
  11. attrs?: Attrs; // 节点 attribute 属性
  12. class?: Classes; // class 类名
  13. style?: VNodeStyle; // style 样式
  14. dataset?: Dataset; // html 自定义属性 data-
  15. on?: On; // 事件
  16. attachData?: AttachData;
  17. hook?: Hooks; // 钩子
  18. key?: Key;
  19. ns?: string; // for SVGs
  20. fn?: () => VNode; // for thunks
  21. args?: any[]; // for thunks
  22. is?: string; // for custom elements v1
  23. [key: string]: any; // for any other 3rd party module
  24. }

h 函数

h 渲染函数实际上只用于创建 vnode,并没有将 vnode 绘制到文档中。作为接口,h 渲染函数本身也只包含参数多态的处理,由于存在多种参数情况,所以首先会对参数进行格式化,在对 children 处理,如果是 svg 元素,调用 addNS 函数处理,最后返回 vnode 。

  1. export function h(sel: string): VNode;
  2. export function h(sel: string, data: VNodeData | null): VNode;
  3. export function h(sel: string, children: VNodeChildren): VNode;
  4. export function h(sel: string, data: VNodeData | null, children: VNodeChildren): VNode;
  5. export function h(sel: any, b?: any, c?: any): VNode {
  6. let data: VNodeData = {};
  7. let children: any;
  8. let text: any;
  9. let i: number;
  10. // 参数格式化
  11. if (c !== undefined) {
  12. if (b !== null) {
  13. data = b;
  14. }
  15. if (is.array(c)) {
  16. children = c;
  17. } else if (is.primitive(c)) {
  18. text = c;
  19. } else if (c && c.sel) {
  20. children = [c];
  21. }
  22. } else if (b !== undefined && b !== null) {
  23. if (is.array(b)) {
  24. children = b;
  25. } else if (is.primitive(b)) {
  26. text = b;
  27. } else if (b && b.sel) {
  28. children = [b];
  29. } else {
  30. data = b;
  31. }
  32. }
  33. // 如果存在 children,将不是 vnode 的项转成 vnode
  34. if (children !== undefined) {
  35. for (i = 0; i < children.length; ++i) {
  36. if (is.primitive(children[i]))
  37. children[i] = vnode(
  38. undefined,
  39. undefined,
  40. undefined,
  41. children[i],
  42. undefined
  43. );
  44. }
  45. }
  46. // svg 元素添加 namespace
  47. if (sel[0] === "s" && sel[1] === "v" && sel[2] === "g" && (sel.length === 3 || sel[3] === "." || sel[3] === "#")) {
  48. addNS(data, children, sel);
  49. }
  50. // 返回 vnode
  51. return vnode(sel, data, children, text, undefined);
  52. }

sameVnode 函数

sameVnode 函数的主要逻辑是用来判断 vnode 节点是否为相同

  1. function sameVnode(vnode1: VNode, vnode2: VNode): boolean {
  2. // key 和 sel 都相等
  3. // 如果 key 没有传入 => undefined === undefined // true
  4. // 不传 key 情况分析:不在循环体里面,像直接定义的情况,直接通过 tag/sel 来比较
  5. const isSameKey = vnode1.key === vnode2.key;
  6. const isSameIs = vnode1.data?.is === vnode2.data?.is;
  7. const isSameSel = vnode1.sel === vnode2.sel;
  8. return isSameSel && isSameKey && isSameIs;
  9. }

createKeyToOldIdx 函数

createKeyToOldIdx 函数 返回 map 将所有定义了 key 的 oldVNode 在数组中的 index 作为键值,key作为键名存储起来,然后赋给 oldKeyToIdx(updateChildren 方法使用)

  1. function createKeyToOldIdx(children: VNode[], beginIdx: number, endIdx: number): KeyToIndexMap {
  2. const map: KeyToIndexMap = {};
  3. for (let i = beginIdx; i <= endIdx; ++i) {
  4. const key = children[i]?.key;
  5. if (key !== undefined) {
  6. map[key as string] = i;
  7. }
  8. }
  9. return map;
  10. }

createElm 函数

大白话

createElm 函数的主要逻辑在于创建真实的 DOM 节点 vnode.elm。首先调用 init hook ,然后创建 DOM 树分为如下三种情形:

  • vnode.sel === ‘!’ 表示当前元素是注释节点,调用 createComment 创建注释节点,挂载到 vnode.elm;
  • vnode.sel !== ‘undefined’,对当前选择器进行解析(tag、id,class等),调用 createElementNS 或 createElement 生成 elm,接着调用 create hook,如果存在 children,遍历所有子节点并递归调用 createElm 创建 dom,通过 appendChild 挂载到当前的 elm 上,不存在 children 但存在 text,便使用 createTextNode 来创建文本,再次调用 create hook 与 insert hook 存储起来等 dom 插入后才会调用,这里用个数组来保存能避免调用时再次对 vnode 树做遍历;
  • 以上两种都不满足时,表示当前只是 text,调用 createTextNode 来创建文本,然后挂载到 vnode.elm;

    流程图

    虚拟DOM(Virtual DOM) 和 diff - 图3

    coding

    ```typescript function createElm(vnode: VNode, insertedVnodeQueue: VNodeQueue): Node { let i: any; let data = vnode.data; if (data !== undefined) { // 调用 init hook const init = data.hook?.init; if (isDef(init)) {

    1. init(vnode);
    2. data = vnode.data;

    } } const children = vnode.children; const sel = vnode.sel; // 注释节点 if (sel === “!”) { if (isUndef(vnode.text)) {

    1. vnode.text = "";

    } // 创建注释节点 vnode.elm = api.createComment(vnode.text!); } else if (sel !== undefined) { // Parse selector const hashIdx = sel.indexOf(“#”); const dotIdx = sel.indexOf(“.”, hashIdx); const hash = hashIdx > 0 ? hashIdx : sel.length; const dot = dotIdx > 0 ? dotIdx : sel.length; const tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel; const elm = (vnode.elm = isDef(data) && isDef((i = data.ns)) ? api.createElementNS(i, tag, data) : api.createElement(tag, data));

    if (hash < dot) elm.setAttribute(“id”, sel.slice(hash + 1, dot)); if (dotIdx > 0) elm.setAttribute(“class”, sel.slice(dot + 1).replace(/./g, “ “));

    // 调用 create hook for (i = 0; i < cbs.create.length; ++i) cbs.createi;

    // 挂载子节点(递归创建节点) if (is.array(children)) {

    1. for (i = 0; i < children.length; ++i) {
    2. const ch = children[i];
    3. if (ch != null) {
    4. api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue));
    5. }
    6. }

    } else if (is.primitive(vnode.text)) {

    1. api.appendChild(elm, api.createTextNode(vnode.text));

    } const hook = vnode.data!.hook; if (isDef(hook)) {

    1. // 调用 create hook
    2. hook.create?.(emptyNode, vnode);
    3. if (hook.insert) {
    4. // insert hook 存储起来 等 dom 插入后才会调用,这里用个数组来保存能避免调用时再次对 vnode 树做遍历
    5. insertedVnodeQueue.push(vnode);
    6. }

    } } else { // 文本节点 vnode.elm = api.createTextNode(vnode.text!); } return vnode.elm; }

  1. <a name="IVqbP"></a>
  2. ### patchVnode 函数
  3. <a name="wJfOf"></a>
  4. #### 大白话
  5. patchVnode 函数的主要逻辑在于更新节点。首先调用 vnode 上的 prepatch hook,如果当前的两个 vnode 完全相同,直接返回。然后分如下两大类情形:
  6. - vnode.text === undefined
  7. - 新旧节点都存在 children,执行 updateChildren 更新
  8. - 存在新 children,不存在旧 children,如果旧 text 存在,则先清空在调用 addVnodes 添加children
  9. - 不存在新 children,存在旧 children,调用 removeVnodes 移除旧节点的 children
  10. - oldVnode 存在 text,调用 setTextContent 置空
  11. - vnode.text !== undefined,移除 oldVnode 对应的 dom 节点,并使用 setTextContent 更新 vnode.elm
  12. <a name="yhXcy"></a>
  13. #### 流程图
  14. ![](https://cdn.nlark.com/yuque/0/2021/jpeg/1544252/1619164370250-dc9e73ff-4f20-479d-a9b2-d58f59cb56e4.jpeg)
  15. <a name="1qVpj"></a>
  16. #### coding
  17. ```typescript
  18. function patchVnode(oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
  19. // 执行 prepatch hook
  20. const hook = vnode.data?.hook;
  21. hook?.prepatch?.(oldVnode, vnode);
  22. // 设置 vnode.elm
  23. const elm = (vnode.elm = oldVnode.elm)!;
  24. // 旧 children
  25. const oldCh = oldVnode.children as VNode[];
  26. // 新 children
  27. const ch = vnode.children as VNode[];
  28. // 如果 oldVnode 和 vnode 是完全相同,说明无需更新,直接返回。
  29. // 极少这种情况,除非人为测试
  30. if (oldVnode === vnode) return;
  31. // hook 相关
  32. if (vnode.data !== undefined) {
  33. // 调用 update hook
  34. for (let i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode);
  35. // 调用 vnode update hook
  36. vnode.data.hook?.update?.(oldVnode, vnode);
  37. }
  38. // 新test(vnode.text) === undefined (vnode.children != undefined) / (vnode.children 一般有值)
  39. // text 与 children 不可能共存,但是都为 undefined 成立
  40. if (isUndef(vnode.text)) {
  41. // 新旧都有 children
  42. if (isDef(oldCh) && isDef(ch)) {
  43. // 新旧节点都存在 children 就执行 updateChildren
  44. if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);
  45. }
  46. // 存在新 children,不存在旧 children(有可能存在旧 text)
  47. else if (isDef(ch)) {
  48. // 如果旧 text 存在值,先置空
  49. if (isDef(oldVnode.text)) api.setTextContent(elm, "");
  50. // 添加 children
  51. addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
  52. }
  53. // 存在旧 children,不存在新 children(有可能存在旧 text)
  54. else if (isDef(oldCh)) {
  55. // 移除旧节点的 children
  56. removeVnodes(elm, oldCh, 0, oldCh.length - 1);
  57. } else if (isDef(oldVnode.text)) {
  58. // 旧节点存在 text 置空
  59. api.setTextContent(elm, "");
  60. }
  61. }
  62. // else: vnode.text !== undefined (vnode.children 无值)
  63. else if (oldVnode.text !== vnode.text) {
  64. // 移除旧 children
  65. if (isDef(oldCh)) {
  66. // 新节点删除了 children ,删除老的 DOM 元素
  67. removeVnodes(elm, oldCh, 0, oldCh.length - 1);
  68. }
  69. // 文本节点更新
  70. api.setTextContent(elm, vnode.text!);
  71. }
  72. // 调用 postpatch hook
  73. hook?.postpatch?.(oldVnode, vnode);
  74. }

updateChildren 函数

大白话

流程图

coding

  1. function updateChildren(parentElm: Node, oldCh: VNode[], newCh: VNode[], insertedVnodeQueue: VNodeQueue) {
  2. let oldStartIdx = 0;
  3. let newStartIdx = 0;
  4. let oldEndIdx = oldCh.length - 1;
  5. let oldStartVnode = oldCh[0];
  6. let oldEndVnode = oldCh[oldEndIdx];
  7. let newEndIdx = newCh.length - 1;
  8. let newStartVnode = newCh[0];
  9. let newEndVnode = newCh[newEndIdx];
  10. let oldKeyToIdx: KeyToIndexMap | undefined;
  11. let idxInOld: number;
  12. let elmToMove: VNode;
  13. let before: any;
  14. // 遍历 oldCh newCh,对节点进行比较和更新
  15. // 每轮比较最多处理一个节点,算法复杂度 O(n)
  16. while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
  17. if (oldStartVnode == null) {
  18. oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
  19. } else if (oldEndVnode == null) {
  20. oldEndVnode = oldCh[--oldEndIdx];
  21. } else if (newStartVnode == null) {
  22. newStartVnode = newCh[++newStartIdx];
  23. } else if (newEndVnode == null) {
  24. newEndVnode = newCh[--newEndIdx];
  25. }
  26. // 以旧 vnode 首节点和新 vnode 首节点对比
  27. else if (sameVnode(oldStartVnode, newStartVnode)) {
  28. patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
  29. oldStartVnode = oldCh[++oldStartIdx];
  30. newStartVnode = newCh[++newStartIdx];
  31. }
  32. // 以旧 vnode 尾节点和新 vnode 尾节点对比
  33. else if (sameVnode(oldEndVnode, newEndVnode)) {
  34. patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
  35. oldEndVnode = oldCh[--oldEndIdx];
  36. newEndVnode = newCh[--newEndIdx];
  37. }
  38. // 以旧 vnode 首节点和新 vnode 尾节点对比
  39. else if (sameVnode(oldStartVnode, newEndVnode)) {
  40. // Vnode moved right
  41. patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
  42. api.insertBefore(
  43. parentElm,
  44. oldStartVnode.elm!,
  45. api.nextSibling(oldEndVnode.elm!)
  46. );
  47. oldStartVnode = oldCh[++oldStartIdx];
  48. newEndVnode = newCh[--newEndIdx];
  49. }
  50. // 以旧 vnode 尾节点和新 vnode 新节点对比
  51. else if (sameVnode(oldEndVnode, newStartVnode)) {
  52. // Vnode moved left
  53. patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
  54. api.insertBefore(parentElm, oldEndVnode.elm!, oldStartVnode.elm!);
  55. oldEndVnode = oldCh[--oldEndIdx];
  56. newStartVnode = newCh[++newStartIdx];
  57. }
  58. // 以上4中都未命中
  59. else {
  60. if (oldKeyToIdx === undefined) {
  61. oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
  62. }
  63. // 拿新节点 key , 能否对应上 oldCh 中的某个节点的 key
  64. idxInOld = oldKeyToIdx[newStartVnode.key as string];
  65. // 对应不上
  66. if (isUndef(idxInOld)) {
  67. // New element
  68. api.insertBefore(
  69. parentElm,
  70. createElm(newStartVnode, insertedVnodeQueue),
  71. oldStartVnode.elm!
  72. );
  73. }
  74. // 对应上了
  75. else {
  76. // 拿到对应上 key 的节点
  77. elmToMove = oldCh[idxInOld];
  78. // sel 是否相等(sameVnode 条件)
  79. // sel 不相等,key相等
  80. if (elmToMove.sel !== newStartVnode.sel) {
  81. // New element
  82. api.insertBefore(
  83. parentElm,
  84. createElm(newStartVnode, insertedVnodeQueue),
  85. oldStartVnode.elm!
  86. );
  87. }
  88. // sel 相等,key 相等
  89. else {
  90. patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
  91. oldCh[idxInOld] = undefined as any;
  92. api.insertBefore(parentElm, elmToMove.elm!, oldStartVnode.elm!);
  93. }
  94. }
  95. // 更新之后,调整指针
  96. newStartVnode = newCh[++newStartIdx];
  97. }
  98. }
  99. // oldCh 已经全部处理完成,而 newCh 还有新的节点,需要对剩下的每个项都创建新的 dom
  100. if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {
  101. if (oldStartIdx > oldEndIdx) {
  102. before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;
  103. addVnodes(
  104. parentElm,
  105. before,
  106. newCh,
  107. newStartIdx,
  108. newEndIdx,
  109. insertedVnodeQueue
  110. );
  111. } else {
  112. // newCh 已经全部处理完成,而 oldCh 还有旧的节点,需要将多余的节点移除
  113. removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
  114. }
  115. }
  116. }

infernojs源码分析

be continued

diff 过程

  • patch
  • patchVnode
  • addVnode
  • removeVnode
  • updateChildren
  • diff算法极致的性能优化做出的改变

vnode.key的作用是什么?

key 是用来判断两个 VNode 是否相同,参与到 diff 过程的一些判断中。key 情况分如下两种情况:

  • 当 key 相同时,调用 patchVnode 函数 更新节点,首先在旧列表中找到对应的 DOM 节点,然后执行移动操作;
  • 当 key 不相同时,会调用 createElm 函数 创建新节点,然后如果存在父节点,便将其插入到 dom 上,然后移除旧的 dom 节点来完成更新;

换句话说,key 用来在一个兄弟节点列表中进行唯一标记,这样在构建新节点的时候,会根据 key 去查找已经存在的节点,而不是就地复用,同时移除 key 不存在的节点。

Vue 与 React实现对比

Vue v2+ 中的 patch 机制就是基于 snabbdom 类库实现的(v3+ 切换到 infernojs)。
React v16+ 在推行 Fiber 之后,摒弃了递归 diff 的做法,但 diff 的核心思想是类似的。

Vue2和Vue3和React三者的diff算法有什么

Vue2:双端比较
Vue3:最长递增子序列
React:仅右移