虚拟 DOM 和 diff 算法


摘要: 介绍 snabbdom、虚拟 DOM、diff 算法

本文重点是什么 ?
1. 虚拟 DOM 是什么,作用 ?
2. 虚拟DOM如何产生 ?—— 以h 函数为例(其实,vue.js中有vnode类,可以创建不同类型的vnode实例)
3. 虚拟DOM最核心,将vnode渲染成真实的DOMpatch 函数

虚拟DOM

简介:

  • 介绍:vnode是一个类,可以生成不同类型的vnode实例,而不同类型的vnode表示不同类型的真实DOM元素。
  • 类型:真实 DOM有元素节点、文本节点和注释节点等。vnode实例也对应有着元素节点、文本节点和注释节点。

(1)注释节点

(2)文本节点

(3)元素节点

(4)组件节点

(5)函数式组件

(6)克隆节点

  • 元素节点为例,存在4个有效属性:

(1)tag:节点名,例如 pul

(2)data:包含节点上的数据,比如attrsclassstyle

(3)children:当前节点的子节点列表

(4)context:当前组件的的vue.js实例

真实的元素节点:

<div class="box">
	<h3>我是一个标题</h3> 
    <ul>
        <li>牛奶</li>
        <li>咖啡</li>
        <li>可乐</li> 
    </ul>
</div> 

对应的vnode实例:

{
    
    
    children:[vnode,vnode],
    context:{
    
    ...},
    data:{
    
    ...},
    tag:"p",
    ...
}

由于Vue.js对组件采用了虚拟DOM来更新视图,当属性发生变化时,整个组件都要进行重新渲染操作,但是组件内并不是所有DOM节点都需要更新,因此,只对需要更新的部分进行DOM操作可以提升很多性能。

虚拟 DOM 做了什么?

其实没有那么复杂,它主要做了两件事。

  1. 提供与真实DOM节点对应的虚拟节点 vnode;
  2. 将虚拟节点vnode和旧的虚拟节点oldVnode进行对比,更新视图。

两个虚拟节点之间的对比是虚拟dom 中最核心的算法(patch),它可以判断出哪些节点发生了变化,从而只对变化的节点进行更新操作。

作用

每次渲染视图时,都是先创建 vnode,然后使用它创建真实的DOM插入到页面中,所以将上一次渲染视图时创建的 vnode存储起来,之后每次重新渲染时,将新旧 vnode 进行对比,

优点:

区别于真实DOM

  • 无需手动操作 DOM,提高开发效率;
  • 复杂场景下,防止频繁触发重绘,可以提高性能;
  • 虚拟 DOM 实际是 JavaScript 对象,可以进行跨平台操作。

h 函数

使用开源库 snabbdom模拟,虚拟DOM 生成vnode

import {
    
     h } from 'snabbdom/h' // helper function for creating vnodes
// 使用 h 函数创建虚拟节点
const vnode = h('ul', {
    
    }, [
  h('li', {
    
    }, 'A'),
  h('li', {
    
    }, 'B'),
  h('li', {
    
    }, 'C'),
  h('li', {
    
    }, 'D')
])

其中,vnode便是h函数创建的虚拟DOMvnode实例

patch 函数

对比两个vode之间的差异只是patch的一部分,只是手段,不是目的。patch的目的是修改DOM节点,可以理解为渲染视图。

操作:创建节点、删除节点、修改节点。

创建节点、删除节点:

两个虚拟节点完全不同,以新节点为标准渲染视图,是需要执行:将旧节点删除或者创建新增节点。

修改节点:

新旧两个节点相同,需要进行细致化对比,然后对oldVode在新视图中对应的真实节点进行更新。

diff 函数

简介:

计算最小更新 DOM 的方式。

  • 将 DOM 抽象为虚拟 DOM ;
  • 然后通过新旧虚拟 DOM 这两个对象的差异( Diff 算法);
  • 最终只把变化的部分重新渲染,提高渲染效率的过程。

流程图:
其中包含 patch 函数、pachVnode 函数、UpdateVnode 函数(未详细介绍)

案例:

使用 snabbdom 虚拟 DOM 库 :snabbdom

/** src/index.js */
import {
    
     init } from 'snabbdom/init'
import {
    
     classModule } from 'snabbdom/modules/class'
import {
    
     propsModule } from 'snabbdom/modules/props'
import {
    
     styleModule } from 'snabbdom/modules/style'
import {
    
     eventListenersModule } from 'snabbdom/modules/eventlisteners'
import {
    
     h } from 'snabbdom/h' // helper function for creating vnodes

// 1、创建出 patch 函数
const patch = init([
  classModule,
  propsModule,
  styleModule,
  eventListenersModule
])
// 2、使用 h 函数创建虚拟节点
const vnode1 = h('ul', {
    
    }, [
  h('li', {
    
    }, 'A'),
  h('li', {
    
    }, 'B'),
  h('li', {
    
    }, 'C'),
  h('li', {
    
    }, 'D')
])
// 3.创建空的容器
const container = document.getElementById('container')
// 4.将 DOM 塞入container
patch(container, vnode1)

const vnode2 = h('ul', {
    
    }, [
  h('li', {
    
    }, 'A'),
  h('li', {
    
    }, 'B'),
  h('li', {
    
    }, 'C'),
  h('li', {
    
    }, 'D'),
  h('li', {
    
    }, 'E')
])

// 点击按钮时,将vnode1变为vnode2
const btn = document.getElementById('btn')
btn.onclick = function () {
    
    
  // 使用最小改变 来达到 新的vnode 覆盖 旧的 vnode2
  patch(vnode1, vnode2)
}

patch 函数

接收参数存在: element(真实 DOM ) 和 vnode(虚拟 DOM ) 两种类型
首次插入:判断 key 和 sel 是相同(2种情况)

1、vnode.key 和 vnode.sel 都相同 2、vnode.key 或 vnode.sel 不同
是否同一个Vnode
结果 更新(调用 pachVnode ) 创建新的DOM,删除老的DOM

pachVnode 函数

虚拟节点 text 和 children 最多只能有一个

1. 保存(旧的相关联)的 DOM 元素 eml 给新的 Vnode(eml 记录新的 Vnode 插入位置)
2. 比较新、旧 vnode 的 children 情况(3种情况)
1、新 vnode === 旧 vnode(全等) 2、新 Vnode 无 text 3、新 Vnode 有 text
结果 不需更新,return 1.新、老 Vnode 都有 children,即调用(updateChildren);
2.老 Vnode 无 children,即添加 children ;
3.新的无 children ,即删除 children
删除 children ,更新新的 text

猜你喜欢

转载自blog.csdn.net/puhuihui/article/details/126072954