什么是虚拟DOM
- 虚拟DOM,是由普通的JS对象来描述DOM对象
- 使用虚拟DOM来描述真实DOM,创建虚拟DOM开销要比真实DOM小很多
为什么要使用Virtual DOM
- 前端开发刀耕火种的时代
- MVVM框架解决视图和状态同步问题
- 模板引擎可以简化视图操作,没办法跟踪状态
- 虚拟DOM跟踪状态变化
参考github上virtual-dom的动机描述
- 虚拟DOM可以维护程序的状态,跟踪上一次的状态
- 通过比较前后两次状态差异更新真实DOM
虚拟DOM的作用
- 维护视图和状态的关系
- 复杂视图情况下提升渲染性能
跨平台
- 浏览器平台渲染DOM
- 服务端渲染SSR(Nuxt.js/Next.js)
- 原生应用(Weex/React Native)
- 小程序(mpvue/uni-app)等
虚拟DOM库
Snabbdom
- Vue.js 2.x内部使用的虚拟DOM就是改造的Snabbdom
- 大约200 SLOC(single line of code)
- 通过模块可扩展
- 源码使用TypeScript开发
- 最快的Virtual DOM之一
- virtual-dom
创建项目
- 安装parcel
- 配置scripts
- 目录结构
安装parcel
- md snabbdom-demo
- cd snabbdom-demo
- npm init -y
- npm install parcel-bundler -D
Snabbdom文档
- 学习任何一个库都要先看文档
- 通过文档了解库的作用
- 看文档中提供的示例,自己快速实现一个demo
- 通过文档查看API的使用
Snabbdom文档
- https://github.com/snabbdom/snabbdom
- 当前版本v2.1.0
- npm install snabbdom@2.1.0
模块的作用
- Snabbdom的核心库并不能处理DOM元素的属性/样式/事件等,可以通过注册Snabbdom默认提供的模块来实现
- Snabbdom中的模块可以用来扩展Snabbdom的功能
- Snaddom中的模块的实现是通过注册全局的钩子函数来实现的
模块的使用步骤
- 导入需要的模块
- init()中注册模块
- h()函数的第二个参数 ```javascript import { init } from ‘snabbdom/build/package/init’ import { h } from ‘snabbdom/build/package/h’
// 1. 导入模块 import { styleModule } from ‘snabbdom/build/package/modules/style’ import { eventListenersModule } from ‘snabbdom/build/package/modules/eventlisteners’
// 2. 注册模块 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’) ])
function eventHandler () { console.log(‘别点我,疼’) }
let app = document.querySelector(‘#app’) patch(app, vnode) ```
如何学习源码
- 宏观了解
- 带着目标看源码
- 看源码的过程要不甚解
- 调试
- 参考资料
Snabbdom的核心
- init()设置模块,创建patch()函数
- 使用h()函数创建JavaScript对象(VNode)描述真实DOM
- patch()比较新旧两个Vnode
- 把变化的内容更新到真实DOM树
h函数介绍
- 作用: 创建VNode对象
- Vue中的h函数
- h函数最早见于hyperscript,使用JavaScript创建超文本
函数重载
- 参数个数或参数类型不同的函数
- JavaScript中没有重载的概念
- TypeScript中有重载,不过重载的实现还是通过代码调整参数
vscode常用快捷键
- F12 或者 Ctrl + 左鼠标 能快速定位到函数的定义位置
- Alt + <- 能立马回到刚才过来的位置
- Alt + -> 能回到前面的位置
patch整体过程分析
- patch(oldVnode, newVnode)
- 把新节点中变化的内容渲染到真实DOM,最后返回新节点作为下一次处理的旧节点
- 对比新旧VNode是否相同节点(节点的key和sel相同)
- 如果不是相同节点,删除之前的内容,重新渲染
- 如果是相同节点,再判断新的VNode是否有text,如果有并且和oldVNode的text不同,直接更新文本内容
- 如果新的VNode有children,判断子节点是否有变化
Diff 算法
虚拟DOM中的Diff算法
- 查找两颗树每一个节点的差异,传统的会把根节点与另一树上每一个节点做比较
Snbbdom根据DOM的特点对传统的diff算法做了优化
- DOM操作时候很少会跨级级操作节点
- 只比较同级别的节点
开始和结束节点
如果新旧开始节点是sameVnode(key和sel相同)
- 调用patchVnode()对比和更新节点
- 把旧开始和新开始索引往后移动 oldStartIdx++/oldEndIdex++
旧开始节点/新结束节点
- 调用patchVnode()对比和更新节点
- 把oldStartVnode对应的DOM元素,移动到右边,更新索引
- 把oldEndVnode对应的DOM元素,移动到左边,更新索引
循环结束
- 当老节点的所有子节点先遍历完(oldStartIdx > oldEndIdx),循环结束
- 新节点的所有子节点先遍历完(newStartIdx > newEndIdx),循环结束
设置key的意义
- 需要给所有有相同父元素的子元素,设置具有唯一值的key,否则的话,有可能造成渲染错误
- 当数据变化时,执行updateChild,内部对比子节点的时候,会判断新旧VNode的key是否相等,如果当前没有key或者相同,会重用当前的DOM元素,标签里的文字内容会更新,但不会产生新的DOM元素
