虚拟 DOM 和 diff 算法

虚拟 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!);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容