文章目录
摘要: 介绍 snabbdom、虚拟 DOM、diff 算法
本文重点是什么 ?
1. 虚拟 DOM
是什么,作用 ?
2. 虚拟DOM
如何产生 ?—— 以h
函数为例(其实,vue.js
中有vnode
类,可以创建不同类型的vnode
实例)
3. 虚拟DOM
最核心,将vnode
渲染成真实的DOM
— patch
函数
虚拟DOM
简介:
- 介绍:
vnode
是一个类,可以生成不同类型的vnode
实例,而不同类型的vnode
表示不同类型的真实DOM
元素。 - 类型:真实
DOM
有元素节点、文本节点和注释节点等。vnode
实例也对应有着元素节点、文本节点和注释节点。
(1)注释节点
(2)文本节点
(3)元素节点
(4)组件节点
(5)函数式组件
(6)克隆节点
- 以
元素节点
为例,存在4个有效属性:
(1)tag:节点名,例如
p
、ul
等(2)data:包含节点上的数据,比如
attrs
、class
和style
等(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 做了什么?
其实没有那么复杂,它主要做了两件事。
- 提供与真实
DOM
节点对应的虚拟节点vnode
; - 将虚拟节点
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
函数创建的虚拟DOM
:vnode
实例
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 |