虚拟DOM的实现原理
虚拟DOM是什么
定义:用普通的js对象来描述DOM对象
虚拟DOM实例:
{
sel:"div", //选择器
data:{}, //模块中所需要的数据
children:undefined, //对应的子节点(children和text属性互斥)
text:"hello", //标签中的文本(children和text属性互斥)
elm:undefined, //当前vnode对象转换之后的DOM元素
key:undefined //唯一标识当前的vnode对象(唯一标识节点),string/number
}
VNodeData接口
- 用来约束data对象的类型。即VNode接口中的data属性
为什么使用虚拟DOM
为什么使用虚拟DOM:
- 复杂视图情况下提升渲染性能
- 创建虚拟DOM的成本比真实DOM小很多(因为虚拟DOM、真实DOM的成员分别为6、上百个)
- 解决跟踪状态变化问题(数据发生变化,可以获取上一次的状态,不用把页面上的所有元素删除再重新创建)
- 跨平台(因为虚拟DOM是js对象, 可以对虚拟DOM做任何的编程处理)
- 浏览器平台渲染DOM
- 服务端渲染SSR(Nuxt.js:是基于vue的SSR /Next.js:是基于react的SSR )
- 服务端渲染就是把虚拟DOM转换成普通的HTML字符串
- 原生应用(Weex/React Native)
- 小程序(mpvue/uni-app)等
前端时代回顾:
- 刀耕火种
- 手动操作DOM、解决浏览器兼容性问题
- jquery
- 简化DOM的复杂操作,跨多种浏览器工作
- jQuery是一个快速、小且功能丰富的JavaScript库。它使用一个易于使用的API,可以跨多种浏览器工作,从而使HTML文档遍历和操作、事件处理、动画和Ajax等工作变得更加简单。jQuery结合了多功能性和可扩展性,改变了数百万人编写JavaScript的方式。
- 项目复杂化
- 既要操作数据又要操作DOM
- 为了简化DOM的复杂操作
- MVVM框架解决视图和状态同步(当数据发生变化自动更新视图,视图发生变化自动同步数据)
- 简化视图操作
- 模板引擎可以简化视图操作,没办法跟踪状态(数据发生变化,无法获取上一次的状态,只能把页面上的所有元素删除再重新创建)
- 解决跟踪状态变化问题
- 虚拟DOM,当状态改变的时候,不需要立即更新DOM,只需要创建一个虚拟DOM树来描述真实的DOM树,虚拟DOM内部会弄清如何有效的更新真实DOM(diff算法找到状态的差异),只更新变化的部分
- 虚拟DOM可以维护程序的状态,跟踪上一次的状态
- 通过比较前后2次状态差异更新真实DOM
- 虚拟DOM,当状态改变的时候,不需要立即更新DOM,只需要创建一个虚拟DOM树来描述真实的DOM树,虚拟DOM内部会弄清如何有效的更新真实DOM(diff算法找到状态的差异),只更新变化的部分
虚拟DOM的实现原理
snabbdom的作用:是一个注重简单性、模块化、具有强大特性和性能的虚拟DOM库
导入snabbdom
snabbdom的核心函数
-
init
- 是一个高阶函数,接收一个数组作为参数,数组中加载的是snabbdom的模块
- init函数返回一个patch函数:把虚拟DOM转换成真实DOM渲染到界面上
-
h(重要)
- 用来创建虚拟节点,(经过设置,创建的节点会替换掉html中的 )
- 有多个参数
- 传2参数时
- 1:字符串类型,代表标签+选择器
- 2
- 如果是字符串:标签中的文本内容
- 如果是数组:数组里的每一个元素都是一个vnode对象,可以继续调用h函数来创建
- 传2参数时
- h(’!’):生成空的注释节点
-
vnode的作用:虚拟DOM,用来描述真实DOM
-
patch函数(重要):对比2个vnode,把2个vnode的差异更新到真实DOM上
- 参数
- 有2个参数,都是vnode。
- 1:旧的vnode
- 可以传递真实DOM,内部会把真实DOM转换成vnode再对比
- 2:新的vnode
- 1:旧的vnode
- 有2个参数,都是vnode。
- 返回值
- 返回新的vnode,返回的vnode会作为下一次再调用patch时的旧的vnode
- 参数
snabbdom模块
模块的作用
- Snabbdom的核心模块只能对vnode进行操作,不能处理DOM元素的属性/样式/事件等(可以通过Snabbdom自带的模块实现)
- Snabbdom的模块是用来扩展Snabbdom本身的功能的
- 类似于插件机制,也可以创建自定义模块来处理自己的逻辑
- Snabbdom中模块的实现是通过注册全局的钩子函数来实现的
- 全局钩子函数:是vnode的整个生命周期的过程中被触发的函数
官方提供的模块(不需记忆只需看懂)
- attributes
- 设置vnode对应的DOM元素的属性,使用的是DOM的标准方法set attributes实现的
- 模块内部会对dom元素的布尔类型的属性作判断(如selected、checked)
- props
- 和attributes类似,设置DOM元素的属性,
- 不同的是
- props是通过 对象.属性 设置的
- 内部不会处理布尔类型的属性
- dataset
- 处理html5中data-这种自定义属性
- class
- 不是用来设置类样式的,用来切换类样式的
- 设置类样式可以通过h函数的第一个参数
- style
- 设置行内样式,容易设置过渡动画,内部注册了transation end事件
- eventlisteners
- 注册和移除事件
其他模块
- hero:自定义模块
- module:定义了模块中使用到的所有的钩子函数
- h:定义了h函数,用来创建vnode对象
- hooks:定义了vnode的生命周期中用到的所有钩子函数
- htmldomapi:对dom api的包装,里面是创建元素、删除元素等
- init:定义了init函数,init用来加载模块、dom api并返回一个patch函数
- is:辅助的模块,导出了2个模块,用来判断数组和原始值的函数
- gsx-global:是gsx的类型声明文件
- gsx:用来处理gsx的
- thunk:用来优化处理对复杂视图不可变值的优化
- tovnode:提供了一个函数,可以把dom转换为vnode
- vnode:定义了vnode的结构,是一个接口,规定了vnode对象应该有哪些成员
模块的使用步骤
- 导入需要的模块
- init()中注册模块
- h()函数的第2个参数(对象形式)处使用模块
看Snabbdom源码的小目标
函数重载
- 函数个数或参数类型不同的函数(和参数有关,和返回值无关)
- js中没有重载的概念
- TypeScript中有重载,不过重载的实现还是通过代码调整参数
看源码快捷键
- 选中模块+F12 / 选中模块+ctrl+鼠标左键:定义到模块定义的位置
- alt+← :返回刚才的位置
- alt+→ :前进到刚才的位置