美文网首页
虚拟 DOM 和 diff 算法

虚拟 DOM 和 diff 算法

作者: my木子 | 来源:发表于2021-04-28 07:52 被阅读0次

    虚拟 DOM(Virtual DOM)

    • 通过 JS 对象表示 DOM 结构,虚拟DOM 是对 DOM 的抽象
    • 通常含有标签名、属性、事件和子元素等属性
    • 虚拟DOM主要是为了提升 少量数据更新 时的性能,通常情况下框架操作 DOM 都不会比原生的 DOM 优化操作慢。
        // html
        <div class="red" onclick="fun()">
            <span>1</span>
            <span>2</span>
        </div>
    
       // Vue 虚拟 DOM 结构
        const vNode = {
            tag: "div",             // 标签名或组件名
            props:{
                className: "red",       // 标签属性    
                on:{
                    click:()=>{ }    // 事件
                }
            },               
            children: [             // 子元素
                {tag: "span",children:"1"},     
                {tag: "span",children:"1"}
            ]
        }
    
        // React 虚拟DOM结构
        const vNode = {
            type: "div",                // 标签名或组件名
            props: {
                children: [             // 子元素
                    {type: "span",props:{children:"1"}},     
                    {type: "span",props:{children:"2"}}
                    ],
                    className: "red",   // 标签属性    
                    onClick:()=>{}      // 事件
            },
            key: null,
            ref: null,
        }
    

    虚拟DOM的优点

    1.减少不必要的DOM操作(数据驱动视图)
    • 减少DOM操作的次数,真实DOM需要依次操作,虚拟DOM 可以将多次操作合并成一次(比如往页面中添加1000个节点)
    • 减少DOM操作的范围(针对性优化),借助DOM diff 算法,计算出最小的更新范围,省去多余的操作(比如页面中有990个节点,在原有基础上新增10个节点)
    2.跨平台
    • 虚拟DOM不仅可以变成DOM,还可以变成小程序、IOS应用、安卓应用等,因为虚拟DOM本质上是JS对象

    虚拟 DOM 的缺点

    • 需要额外的创建函数(createElement、h),并通过编译工具进行编译React(Babel)、Vue( vue-loader,因为不是js文件所以不能用 Babel 编译)

    diff 算法(虚拟 DOM 的核心)

    • diff 算法是虚拟 DOM 技术的必然产物:通过新旧虚拟 DOM 做对比(即 diff),将变化的地方更新在真实的 DOM 上,另外,也需要 diff 高效的执行对比过程,从而降低复杂度。

    diff 过程(深度优先,同层比较)

    • 同级比较,减少比较次数
    • 比较标签名,标签名不同直接删除,不继续深度比较
    • 标签名相同时比较 key,如过 key 相同就认为是相同节点不继续深度比较了。(key 的作用是为了高效的更新虚拟 DOM,原理:Vue 在 patch 过程中通过 key 可以精准判断两个节点是否是同一个,从而避免频繁的更新不同元素,使得整个 patch 过程更高效,减少 DOM 操作,提高性能)。
    diff 算法

    Snabbdom

    • Vue 中的虚拟DOM参考 Snabbdom
    // html
    <div id="container"></div>
    // js
    import {init,classModule,propsModule,styleModule,eventListenersModule,h} from "snabbdom";
    
    const patch = init([
      // Init patch function with chosen modules
      classModule, // makes it easy to toggle classes
      propsModule, // for setting properties on DOM elements
      styleModule, // handles styling on elements with support for animations
      eventListenersModule, // attaches event listeners
    ]);
    const container = document.getElementById("container");  // 创建容器
    console.log(container)
    
    //  创建 vnode 虚拟节点
    const vnode = h("ul#list", { }, [
      h("li.item", { }, "第一项"),
      h("li.item", { }, "第二项")
    ]);
    
    patch(container, vnode);  // 将 vnode 虚拟节点放到 container 容器中
    
    const newVnode = h("ul#list", { }, [
      h("li.item", { }, "第一项"),
      h("li.item", { }, "第二项改变"),
      h("li.item", { }, "第三项新增"),
    ]);
    
    setTimeout(()=>{
        patch(vnode, newVnode); // 2秒后更新视图
    },2000);
    

    生成 vnode

    1. 执行 h 函数判断传入的值,生成vnode。

    patch 函数

    1. 先判断是不是vnode,首次渲染不是vnode,会通过 emptyNodeAt 函数创建空的 vnode 并关联DOM元素;
    2. 然后再通过 sameVnode 函数比较标签和 key 是否相等,来判断 oldVnode 和 vnode 是不是相同的 vnode,如果相同通过 patchVnode 函数更新视图;如果不同则通过 createElm 函数创建新的DOM元素,并且删除老的DOM元素。
       if (!isVnode(oldVnode)) { // 首次渲染不是vnode
    
          oldVnode = emptyNodeAt(oldVnode);  // 创建空的 vnode 并关联DOM元素
    
        }
    
        if (sameVnode(oldVnode, vnode)) {  // 比较标签和 key 是否相等,来判断 oldVnode 和 vnode 是不是相同的 vnode
    
          patchVnode(oldVnode, vnode, insertedVnodeQueue);  // 相同则函数更新视图
    
        } else {
          elm = oldVnode.elm!;
          parent = api.parentNode(elm) as Node;
    
          createElm(vnode, insertedVnodeQueue);  // 创建新的DOM元素
    
          if (parent !== null) {
            api.insertBefore(parent, vnode.elm!, api.nextSibling(elm));
            removeVnodes(parent, [oldVnode], 0, 0); // 删除老的DOM元素
          }
        }
    

    patchVnode 函数

    1. 先给新的 vnode 绑定 oldVnode DOM元素,获取 oldVnode 的 children,如果相等则不需要做任何操作;如果不相等则判断是否是 text 还是 children ;
    2. 如果有 text,则移除 children 并比较是否更新 text ;
    3. 如果无 text(4种情况)
      (1) vnode 和 oldVnode 都有 children 则调用 updateChildren 函数更新视图;
      (2) vnode 有 children ,oldVnode 无 children 有 text,则清空 text 并通过 addVnodes 函数添加 children;
      (3) vnode 无 children ,oldVnode 有 children,则通过 removeVnodes 函数移除 children;
      (4) vnode 无 children ,oldVnode 有 text,则清空 text。
        if (isUndef(vnode.text)) {  // vnode 无 text
    
          if (isDef(oldCh) && isDef(ch)) {  // vnode 和 oldVnode 都有 children 
    
            if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);  // 更新视图
    
          } else if (isDef(ch)) {  // vnode 有 children ,oldVnode 无 children 有 text
    
            if (isDef(oldVnode.text)) api.setTextContent(elm, "");  // 清空 text 
            addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);  // 添加 children
    
          } else if (isDef(oldCh)) { // vnode 无 children ,oldVnode 有 children
    
            removeVnodes(elm, oldCh, 0, oldCh.length - 1);  // 移除 children
    
          } else if (isDef(oldVnode.text)) {  // vnode 无 children ,oldVnode 有 text
    
            api.setTextContent(elm, "");  // 清空 text
    
          }
        } else if (oldVnode.text !== vnode.text) {  // vnode 有 text
          if (isDef(oldCh)) {
            removeVnodes(elm, oldCh, 0, oldCh.length - 1);
          }
          api.setTextContent(elm, vnode.text!);
        }
    

    相关文章

      网友评论

          本文标题:虚拟 DOM 和 diff 算法

          本文链接:https://www.haomeiwen.com/subject/vopwlltx.html