虚拟dom_虚拟 DOM 和 DOM diff

虚拟 DOM 是什么

一个能代表DOM树的对象,通常含有标签名标签上的属性事件监听和子元素及其它属性。

虚拟DOM在Vue和React中的示例:

const rNode = {    //React
  key: null,
  props: {
    children: [  // 子元素
       { type: 'span', ... }, 
       { type: 'span', ... }
    ],
    className: "red" // 标签上的属性
    onClick: () => {} // 事件
  },
  ref: null,
  type: "div", // 标签名 or 组件名
  ...
}
const vNode = {     //Vue
  tag: "div", // 标签名 or 组件名
  data: {
    class: "red", // 标签上的属性
    on: {
      click: () => {} // 事件
    }
  },
  children: [ // 子元素们
    { tag: "span", ... },
    { tag: "span", ... }
  ],
  ...
}

如何创建虚拟DOM

//React.createElement
createElement('div',{className:'red',onClick:()=> {}},[
    createElement('span', {}, 'span1'),
    createElement('span', {}, 'span2')
  ]
)

//Vue(只能在 render 函数里得到 h)
h('div', {
  class: 'red',
  on: {
    click: () => { }
  },
}, [h('span',{},'span1'), h('span', {}, 'span2'])

用 JSX 简化创建虚拟 DOM

//React JSX
<div className="red" onClick="{()=> {}}">
    <span>span1</span>
    <span>span2</span>
</div>

//Vue Template
h('div', {
  class: 'red',
  on: {
    click: () => { }
  },
}, [h('span',{},'span1'), h('span', {}, 'span2'])

现在创建虚拟 DOM 的方法

//React
<div className="red" onClick={fn}>
    <span>span1</span>
    <span>span2</span>
</div>
//通过 babel 转为 createElement 形式


//Vue Template
<div class="red" @click="fn">
  <span>span1</span>
  <span>span2</span>
</div>
//通过 vue-loader 转为 h 形式

关于虚拟DOM的谣言

DOM操作慢?虚拟DOM快?

我们来仔细分析下这句话的适用场景,这句话就好比“刘翔矮(对比于姚明来说)”,同样DOM操作慢是对比于JS原生API,如数组操作。任何基于DOM的库(Vue/React)都不可能在操作DOM时比DOM快。

为什么大家这么说呢?因为在某些情况下,虚拟DOM快。

假如一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性attach到DOM树上,通知浏览器去执行绘制工作,这样可以避免大量的无谓的计算量。

虚拟 DOM 的优点 :

  • 减少了DOM操作
    • 虚拟DOM可以将多次操作合并为一次操作,比如添加1000个节点(减少频率)。
    • 虚拟DOM借助DOM diff可以把多余的操作省略掉,比如你添加1000个节点,其实只有10个是新增的(减少范围)。
  • 跨平台
    • 虚拟DOM不仅可以变成DOM,还可以变成小程序、ios/安卓应用,应为虚拟DOM本质上只是一个JS对象。

虚拟 DOM 的缺点

  • 需要额外创建函数,如createElement或h,但是可以通过JSX来简化成XML的写法。

DOM diff (虚拟 DOM的对比算法)

DOM diff其实就是一个函数,你可以称它为patch

patches = patch(oldVNode, newVNode)

你可以把虚拟DOM想象成一棵大树的形状,画面自己脑补把!

假设有如下的代码:

<div :class="x">
    <span v-if="y">{string1}</span>
    <span>{string2}</span>
</div>

结合下面的树形结构:

57dc3d0ffe3c6b8c9746b506f2af1c3a.png

当数据变化时

d8997c4495e94871aac01ad23fb3cc95.png
x 从 red变成green

DOM diff发现:div标签类型没变,只需要更新 div 对应的 DOM 的属性;子元素没变,不更新。

9ce1114d0283fa07bf568d42294bcaad.png
y 从 true变成false

DOM diff发现:div 没变,不用更新;子元素1标签没变,但是children变了,更新 DOM 内容;子元素2不见了,删除对应的 DOM。

通过上面的分析研究,猜测出DOM diff 的大概逻辑是:

  • Tree diff

将新旧两棵树逐层对比,找出哪些节点需要更新;如果节点是组件就看 Component diff;如果节点是标签就看 Element diff。

  • Component diff

如果节点是组件,就先看组件类型;类型不同直接替换(删除旧的);类型相同则只更新属性;然后深入组件做 Tree diff(递归)。

  • Element diff

如果节点是原生标签,则看标签名;标签名不同直接替换,相同则只更新属性;然后进入标签后代做 Tree diff(递归)。

但是DOM diff 存在一个问题(key),在同级节点对比存在bug

如果你现在有3个相同类型的同级标签,当删除第2个的时候,我们的预期是当前操作后只剩下1和3,但是计算机不会和我们一样去解决问题,它会理解为你把第2个改为了第3个,然后再把第3个删除。怎么解决呢?
在每一个相同类型的同级标签上添加一个唯一的key,这样DOM diff就会明确地知道你操作的是哪个元素。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值