3-1-4虚拟DOM实现原理

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值