1.课程目标
了解什么是虚拟DOM,虚拟DOM的作用
Snabbdom的基本使用
Snabbdom 的源码解析
2.什么是虚拟DOM
用普通js对象面试DOM对象
创建虚拟dom的开销比真实dom小很多
{
sel
data
childrem
text
ele
key
}
3.为什么使用虚拟DOM
1.前端开发开始粗糙 项目复杂,操作也复杂
2.出来了MVVM框架 视图和状态同步
3.模板引擎可以简化视图操作,没法跟踪状态 变化无法得知上一步的状态
删除再创建
4.虚拟DOM跟踪状态变化
有效的更新真实DOM 只更新变化的部分
5.参考github 的 virtual-dom
可以维护程序的状态,跟踪上一次的状态
通过比较前后两次状态差矣更新真实DOM
4.虚拟DOM的作用和虚拟DOM 库
1.不是所有使用虚拟DOM提高性能 简单的就没必要用虚拟dom
1.维护视图和状态的关系
2.复杂视图情况下提升渲染性能
3.跨平台
浏览器平台渲染DOM
服务端渲染SSR
原生应用 weex
小程序 mpvue/uni-app
虚拟DOM的作用
virtual-dom 最早的
Snabbdom 主要研究这个 200多行源代码
5.Snabbdom的基本使用
parcel 打包工具
配置script
目录结构
6.导入snabbdom
学任何库看文档 了解库的作用
自己快速实现一个demo
导入 snabbdom库
导入两个函数 init h
注意正确的路径
const patch = init([])
7.Snabbdom的基本使用
h函数
h(标签或者选择器,标签的文本内容) 新建标签
patch(旧的vnode,新的vnode) 对比两个vnode 将差别放到更新到vnode上去 渲染到页面上去
可以延时使用
patch(oldVnode, h('!')) 清空div的内容 并且变成注释节点
8.snabbdom模块
模块的作用
不能处理DOM元素的属性/样式/时间等,可以通过注册snabbdom默认提供的模块实现
模块可以用来扩展snabbdom的功能
模块是通过注册全局钩子函数来实现
官方的模块
attributes props dataset class style eventlisteners
模块的使用
1.导入需要的模块
import { styleModule } from ‘snabbdom/build/package/modules/style’
import { eventListenersModule } from ‘snabbdom/build/package/modules/eventlisteners’
2.init()中注册模块
const patch = init([
styleModule,
eventListenersModule
])
3.h()函数的第二个参数处使用模块
let vnode = h('div', [
h('h1', { style: { backgroundColor: 'red' } }, 'Hello World'),
h('p', { on: { click: eventHandler } }, 'Hello P')
])
9.snabbdom 源码分析
宏观了解
带着目标看源码
看远的过程要不求甚解
调试
参考资料
核心流程
1.init()设置模块,创建path()函数的第二个参数处使用模块
2.使用h()函数创建js对象vnode描述真是DOM
3.path()比较新旧两个Vnode
4.把变化的内容更新到真实DOM树
源码
示例中方法 on: { click: eventHandler } 用了数组 改成箭头函数可行
源码的结构分析
10.源码解析-----h函数
场景VNODE对象
Vue里就用了h函数
h函数最早于 hyperscript
使用js创建超文本
函数的重载:
1.参数个数或参数类型不同的参数
2.js中没有重载的概念
3.ts中有重载,重载的实现通过代码调整参数
h(a,b?,c?)
判断c是否存在 显示内容
是否是数组 判断是否多个vnode
是否是字符串或者数字 vnode 的第四个参数 text
判断b是否存在
返回vnode函数 创建vnode对象
11.看源码的快捷键
vscode
f12
alt 方向键
ctrl+鼠标左键
12.源码分析-vnode
vscode
key string|number 标识对象
Vnode 接口
sel 选择器
data
children 与text互斥 只有一个 树结构
elm
text 与children互斥 文本内容
key 接口属性
VnodeData 接口 data属性
vnode 方法
sel
data
children
text
elm
少了key
通过data.key赋值
13.渲染真实Dom的过程 snabbdom的核心
patch 在 init中 俗称打补丁
1.patch(oldVnode,newVnode)
2.把新节点中变化的内容渲染到真实的DOM,最后返回新节点作为下一次处理的旧节点
3.对比新旧VNode是否相同节点(节点的key和sel相同)
4.如果不是相同节点,删除之前的内容,重新渲染
5.如果是相同节点,再判断新的VNode是否有text,如果有并且和oldVnode的text不同,更新文本内容
6.如果新的VNode有children,判断子节点是否变化
13.源码分析 init
结构 入参和内部初始化
init(modules,domApi)
钩子函数
modules 模块数组
domApi:Vnode对象转换成其他环境的元素 默认浏览器dom元素
cbs 钩子函数数组
遍历模块数组 把钩子函数数组赋值到cbs里面
...内部函数
返回 patch函数
好处是 patch 本来要传 modules 和domApi oldVnode newVnode 现在只需要传递后两个
14.源码分析 —patch
看如何对比vnode的差异和返回新vnode
pathch(oldVnode,newVnode)
oldVnode
newVnode
内部结构
elm
parent
insertedVnodeQueue 插入节点队列
触发钩子函数
方法
isVnode 是否有sel属性 就是Vnode对象
emptyNodeAt 将dom对象转化为Vnode对象
sameVnode 判断节点是否相同 判断key 和sel
patchVnode(oldVnode,newVnode,insertedVnodeQueue)
!一定有值
parentNode 返回node对象parentNode属性 获取父节点
createElm 调用钩子
有父节点 调用insertBefore 把新节点对应的元素调到老节点dom元素之后
removeVnoed 然后移除旧节点
createElm
调用insert钩子
遍历post钩子函数
返回新节点
15.调试patch函数
就把流程走一遍
16.源码解析 patch中的 ------createElm
patchVnode
createElm
removeVnoeds
createElm
把vnode节点转换成对应的dom元素 把dom元素放在vnode对象的ele元素中 。并没有放在dom树上
再insertbefore放到parent上 也就是dom树上
1.执行用户设置的init 钩子函数
2.把vnode渲染成真实dom 赋值到vnode.elm上
判断sel是否有值 是否是! 是就赋值 空 清空
没有就创建
有就判断 是字符串 直接添加
不是字符串 则创建节点并且拼接节点
3.返回vnode
insertBefore执行完 body上面就有 页面可以看到新增的节点了
removeVnoeds 移除旧节点
17.removeVnoeds和 addVnodes 批量移除和增加Vnodes
removeVnoeds 移除旧节点
removeVnoeds(parent,[oldVnode],0,0) 后面两个是开始 结束 索引
如果是节点
invokeDestroyHook destroy钩子函数
createRmCb(ch.elm, listeners) 防止重复删除 创建删除节点的函数
listeners 钩子函数的个数 + 1 这个等于0 才创建 删除节点的函数
removeHook 移除钩子函子执行完再调用 删除节点的函数
addVnodes
addVnodes(parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue)
位置
api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before);
变成节点
18.patchVnode 比较新旧两个Vnode 是对相同节点的比较,已从sameVnodes方法中进来
触发perpatch钩子函数
判断新旧节点是否相同
触发update钩子函数
新节点有不等于旧节点的text属性
如果老节点有children 移除老节点对应的DOM元素 removeVnodes
设置新节点对应DOM元素的textContent setTextContent
新老节点如果都有children 且不相等
调用 updateChildren()
对比子节点,并且更新子节点的差异
只有新节点有children属性
如果老节点有text属性 清空对应DOM元素的textContent setTextContent
添加所有子节点 addVnodes
只有老节点有children
清除所有老节点 removeVnodes
只有老节点有text属性
清空对应DOM元素的textContent setTextContent
触发postpatch钩子函数
19.patchVnode里面的 updateChildren 始终将新的节点覆盖到旧节点 如果有相同的则废物利用 减少资源的开销
对比差异更新DOM
Diff算法
虚拟DOM的Diff算法
查找两棵树每一个节点的差异
把第一颗树的节点与第二课树的每一个节点比较 2的n次方
DOM操作很少跨级别操作节点
所以只用比较同级别节点
执行过程
再对开始和结束节点比较的时候,有四种情形 sameVnode比较是否相同 patchVnode 对比新旧差异和更新节点
1.旧开始和新开始
如果两者相同 则依次向后比较
2.如果不同 则从 旧结束和新结束比较
旧开始和新结束
如果相同 旧开始节点移动到旧结束之后
旧结束和新开始
如果相同 把就结束节点移动到最前面 依次比较
非上述情况
以新开始节点依次和旧节点比较
如果不同 则创建新节点 并放到最前面
如果相同 则比较差异 并赋值给变量 插到旧节点最前面
循环结束 以下情景结束
当老节点所有子节点先遍历完
将剩余新节点插入到旧节点后面
当新节点所有子节点先遍历完
老节点剩余的节点删除
20.updateChildren 源码分析
节点比较相同 则重用
OldKeyToIdx 根据老节点的key找索引
21.调试 updateChildren key值
key相同 重用DOM元素 只是移动DOM元素 不会渲染失败
key不设置 重用DOM元素
但是对元素进行文本清除 赋值 数据会更新 但是属性不变 ,导致渲染错误
eg;打勾的1 变成了打勾的100