虚拟DOM
JavaScript对象描述真实DOM
- 真实DOM的属性很多,创建DOM节点开销很大
- 虚拟DOM只是普通JavaScript对象,描述属性并不需要很多,创建开销很小
- 手动操作DOM很难跟踪以前的DOM状态,而虚拟DOM可以跟踪上一次状态,维护程序的状态信息。
虚拟DOM的作用
- 维护视图与状态的关系
- 复杂视图情况下,可以提升渲染性能
- 还可以实现跨平台渲染,如SSR、原生应用、小程序等
虚拟DOM开源库
- Snabbdom
- virtual-dom
Snabbdom
基本使用
snabbdom的几个核心函数
- init:初始化函数,接收一个数组作为参数,返回patch函数
- patch:用来对新旧两个Vnode进行比对,并将差异更新到真实DOM,返回新的Vnode
- 接收两个参数,分别为需要比对的Vnode,其中可以是真实DOM节点(会被转换为Vnode再比对)
- h:创建Vnode
基本流程
- 使用init初始化,得到patch函数
- 使用h函数创建Vnode
- 获取挂载点DOM元素
- 使用patch函数比对挂载点与Vnode,更新到DOM中,并记录当前Vnode(挂载点的DOM被替换为Vnode)
- 之后需要再次更新DOM时,则创建新的Vnode,并传入patch中。
// div中放置子元素h1, p
import { init, h } from 'snabbdom'
let patch = init([])
let vnode = h('div#container', [
h('h1', 'hello snabbdom'),
h('p', '这是一个p标签')
])
let app = document.querySelector('#app')
let oldVnode = patch(app, vnode)
setTimeout(() => {
vnode = h('div#container', [
h('h1', 'hello world'),
h('p', '更新了')
])
patch(oldVnode, vnode)
//清空页面
// patch(oldVnode, null),这种方式是错误的
// 创建注释节点h('!')
patch(oldVnode, h('!'))
}, 2000);
snabbdom中的模块
snabbdom的核心库并不能处理元素的属性/样式/事件等,所以如果需要,可以使用模块
常用模块
官方提供了6个模块
- attributes
- 设置DOM元素的属性,使用的是
setAttribute()
- 处理布尔类型的属性
- 设置DOM元素的属性,使用的是
- props
- 设置DOM元素的属性,但使用
element[attr] = value
- 不处理布尔类型的属性
- 设置DOM元素的属性,但使用
- class
- 切换类样式
- 注意:给元素设置类样式是通过
sel
选择器
- dataset
- 设置
data-*
的自定义属性
- 设置
- eventlisteners
- 注册和移除事件
- style
- 设置行内样式,支持动画
- delayed/remove/destroy
使用模块
模块的使用类似于插件机制。
- 导入需要的模块
init()
中注册模块,init函数接收的数组参数就是来传入模块- 使用
h()
函数创建Vnode的时候,可以把第二个参数设置为对象(通过该对象来设置行内样式、事件等等),其他参数往后移 - 模块按需导入
- 模块实现的核心是基于Hooks
源码解读
看源码必备快捷键
这些快捷键鼠标右键弹出菜单也可以找到。
- 光标移动到某个变量处,按F12快速定位到该变量的定义位置。
- ALT + 左方向键,回到上次的代码位置
- Ctrl + 单击,跳转到某个变量的定义处
- 选中某个变量或方法名,按F12显示出该变量或方法的具体代码
源码分析
snabbdom的核心在于DOM树和元素节点的操作(创建、插入、删除),至于其他如样式、事件等操作,都分布到module中来处理。
h()
函数:主要是根据传入的参数,调用vnode()
函数,创建虚拟节点,返回虚拟节点vnode()
函数:根据传入的参数,返回一个JavaScript对象,基本上是由传入的参数组成的对象patch(oldVnode, newVnode)
函数:将Vnode渲染为真实DOM。是snabbdom的核心函数。- 打补丁,即把新节点变化的内容渲染到真实DOM,最后返回新节点(作为下次处理的旧节点)
- 对比新旧节点是否相同(节点的key和sel是否相同)
- 如果不是相同节点,删除之前的内容,重新渲染
- createElm用来创建DOM节点
- 如果是相同节点,判断newVnode是否有text,如果有且和oldVnode的text不同,则直接更新文本内容
- 如果newVnode有children,判断子节点是否有变化,判断子节点的过程使用的就是diff算法
- diff算法只进行同层级比较
init()
函数:接收modules和domApi两个参数,返回patch函数。- 遍历每个module的钩子函数,将其存储到cbs对象中等待后续调用。
patchVnode()
函数:对比两个Vnode,更新DOM,如果Vnode相同,则调用updateChildren来对比子节点。updateChildren()
:核心的diff算法,通过比较相同节点的子节点,来尽可能复用DOM节点,减少DOM节点的操作。这里通过key属性来更好地查找可能复用的DOM节点。