虚拟 DOM

虚拟 DOM 是什么?

虚拟DOM(Virtual DOM)它是使用javaScript对象来描述真实DOM,虚拟DOM的本质就是javaScript对象,使用javaScript对象来描述DOM的结构,通常含有标签名、标签上的属性、事件监听和子元素们,以及其他属性。程序的各种状态变化首先作用于虚拟DOM,最终映射到真实DOM上

虚拟 DOM 的优点?

  • 减少 DOM 操作 (减少频率)
    • 虚拟 DOM 可以将多次操作合并为一次操作(减少频率)
      • 例一
        1. 比如我要添加 1000 个节点,却是一个接一个操作的
        2. 使用虚拟 DOM就可以减少操作一次性添加
    • 虚拟 DOM 借助 DOM diff 可以把多余的操作省掉(减少范围)
      • 例二
        • 比如你添加 1000 个节点,其实只有 10 个是新增的(减少范围)
  • 跨平台
    • 虚拟 DOM 不仅可以变成 DOM,还可以变成小程序、iOS 应用、安卓应用,因为虚拟 DOM 本质上只是一个 JS 对象

      虚拟 DOM 的缺点?

      需要额外的创建函数,如 createElement 或 h,但可以通过 JSX 来简化成 XML 写法,但是使用JSX就会严重需要依赖JSX文件

      为什么操作 dom 性能开销大 ,而不是查询 dom 树性能开销大

      原因:
  1. dom树的实现模块 和 js 模块 是分开的,这些跨模块的通讯增加了成本。
  2. dom 操作引起的浏览器的回流和重绘,使得性能开销巨大。

备注:在 pc 端是没有性能问题的(因为 pc 的计算能力强),但随着移动端的发展,越来越多的网页在智能手机上运行,而手机的性能参差不齐,会有性能问题

虚拟dom的意义

  • vdom 的真正意义是为了实现跨平台,服务端渲染(从而诞生了react native等);
  • 提供一个性能还算不错 Dom 更新策略;
  • vdom 让整个 mvvm 框架灵活了起来。

    如何创建虚拟 DOM ?

    Vue

    只能在 render 函数里得到 h ```javascript h(‘div’, { class: ‘red’, on: { click: () => { } }, }, [h(‘span’,{},’span1’), h(‘span’, {}, ‘span2’])
  1. <a name="IHpMc"></a>
  2. ### React
  3. React.createElement
  4. ```javascript
  5. createElement('div',{className:'red',onClick:()=> {}},[
  6. createElement('span', {}, 'span1'),
  7. createElement('span', {}, 'span2')
  8. ]
  9. )

虚拟 DOM 的样子

Vue

  1. const vNode = {
  2. tag: "div", // 标签名 or 组件名
  3. data: {
  4. class: "red", // 标签上的属性
  5. on: {
  6. click: () => {} // 事件
  7. }
  8. },
  9. children: [ // 子元素
  10. { tag: "span", ... },
  11. { tag: "span", ... }
  12. ],
  13. ...
  14. }
  15. ------------------------------------------------------------------------
  16. 添加一个 div,他的 class red,在 div 中有两个子元素,是span

React

  1. const vNode = {
  2. key: null,
  3. props: {
  4. children: [ // 子元素们
  5. { type: 'span', ... },
  6. { type: 'span', ... }
  7. ],
  8. className: "red" // 标签上的属性
  9. onClick: () => {} // 事件
  10. },
  11. ref: null,
  12. type: "div", // 标签名 or 组件名
  13. ...
  14. }
  15. ------------------------------------------------------------------------
  16. 添加一个 div,他的 class red,在 div 中有两个子元素,是span

如何简化创建虚拟 DOM的书写?

Vue Template

  1. h('div', {
  2. class: 'red',
  3. on: {
  4. click: () => { }
  5. },
  6. }, [h('span',{},'span1'), h('span', {}, 'span2'])
  7. ------------------------------------------------------------------------
  8. 必须通过通过 vue-loader 转为 h 形式

React JSX

  1. <div className="red" onClick="{()=> {}}">
  2. <span>span1</span>
  3. <span>span2</span>
  4. </div>
  5. ------------------------------------------------------------------------
  6. 必须通过 babel 转为 createElement 形式
  1. js 的插入用时是很快的,只是浏览器渲染的时间,没有那么快,并在渲染的时候会让页面不可交互
  2. 规模较小的时候虚拟 DOM 是较快的,但是规模非常大的时候,还是 JS 的使用正常,使用虚拟 DOM 会崩的

    1. Vue 在非常大的规模使用虚拟 DOM 好像没有崩

      DOM diff

      DOM diff 是什么?

      DOM diff图示

  3. 把虚拟 DOM 想象成树形 ,当数据变化时(从 red 变成green)

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

虚拟 DOM 和 DOM diff - 图1

  1. 把虚拟 DOM 想象成树形 ,当数据变化时(y 从 true 变成 false)
    • div 没变,不用更新
    • 子元素1标签没变,但是children变了,更新 DOM 内容
    • 子元素2不见了,删除对应的 DOM

虚拟 DOM 和 DOM diff - 图2

diff-虚拟 DOM 的对比算法

  • 就是一个函数,称之为patch
  • patches = patch(oldVNode, newVNode)
    • oldVNode旧虚拟节点
    • newVNode新虚拟节点
    • 两个节点进行对比
  • patches 就是要运行的 DOM 操作,可能长这样(伪代码)

    1. [
    2. {type: 'INSERT', vNode: ... },
    3. {type: 'TEXT', vNode: ... },
    4. {type: 'PROPS', propsPatch: [...]}
    5. ]

    DOM diff 的基本逻辑

    Component diff (组件)

  • 如果节点是组件,就先看组件类型

  • 类型不同直接替换(删除旧的)
  • 类型相同则只更新属性
  • 然后深入组件做 Tree diff(递归)

    Element diff (标签)

  • 如果节点是原生标签,则看标签名

  • 标签名不同直接替换,相同则只更新属性
  • 然后进入标签后代做 Tree diff(递归)

    Tree diff

  • 将新旧两棵树逐层对比,找出哪些节点需要更新

  • 如果节点是组件就看 Component diff
  • 如果节点是标签就看 Element diff

    DOM diff 的优点?

    相互对比差异,减少DOM操作

    DOM diff 的问题(key)

  • 不使用 key,或者使用 index 作为可以会有 bug

    • 如果你用 index作为key,那么在删除第二项的时候, index就会从1,2,3变成1,2(因为 index永远都是连续的,所以不可能是1,3),那么ue依然会认为你删除的是第三项。
    • 会使第三项消失
    • 永远不要使用 index 作为 key
  • 修改 bug
    • 创建 id,使用 id 作为 key
    • 这样DOM diff 在对比的时候更加准确,通过对比删除自己想要的东西
    • 更加精准