Virtual DOM爬坑之路

虚拟DOM

什么是 Virtual DOM

  • Virtual DOM(虚拟 DOM),是由普通的 JS 对象来描述 DOM 对象,因为不是真实的 DOM 对象,所以叫 Virtual DOM
  • 真实 DOM 成员
let element = document.querySelector('#app')
let s = ''
for (var key in element) {
  s += key + ','
}
  • 可以使用 Virtual DOM 来描述真实 DOM,示例
 {
   sel: "div",
   data: {},
   children: undefined,
   text: "Hello Virtual DOM",
   elm: undefined,
   key: undefined
 }

为什么使用 Virtual DOM

  • 手动操作 DOM 比较麻烦,还需要考虑浏览器兼容性问题,虽然有 jQuery 等库简化 DOM 操作,但是随着项目的复杂 DOM 操作复杂提升
  • 为了简化 DOM 的复杂操作于是出现了各种 MVVM 框架,MVVM 框架解决了视图和状态的同步问题
  • 为了简化视图的操作我们可以使用模板引擎,但是模板引擎没有解决跟踪状态变化的问题,于是 Virtual DOM 出现了
  • Virtual DOM 的好处是当状态改变时不需要立即更新 DOM,只需要创建一个虚拟树来描述 DOM, Virtual DOM 内部将弄清楚如何有效(diff)的更新 DOM

虚拟 DOM 的作用

  • 维护视图和状态的关系
  • 复杂视图情况下提升渲染性能
  • 除了渲染 DOM 以外,还可以实现 SSR(Nuxt.js/Next.js)、原生应用(Weex/React Native)、小程序(mpvue/uni-app)等

虚拟DOM库:snabbdom

来了解一下虚拟dom库snabbdom吧!

  1. 首先安装
   // 创建 package.json
  npm init -y
  // 安装 parcel、snabbdom
  npm install parcel-bundler -D
  npm install snabbdom@2.1.0
  1. 配置package.json
  "scripts": {
    "dev": "parcel index.html --open",
    "build": "parcel build index.html"
  }
  1. 初步使用
// snabbdom中的模块:
/*
1、模块的作用:Snabbdom的核心模块并不能处理DOM元素的属性/样式/事件等,可以通过注册Snabbdom默认提供的模块来实现;Snabbdom中的模块可以用来扩展Snabbdom的功能;Snabbdom中的模块的实现是通过注册全局的钩子函数来实现的。
2、官方提供的模块:attributes、props、dataset、class、style、eventlisteners
3、使用的步骤:导入需要的模块;init()注册模块;h()函数的第二个参数处使用模块
4、snabbdom的核心:init()设置模块,创建patch()函数;使用h()函数创建JavaScript对象(vnode)描述真实DOM;patch()比较新旧两个VNode;把变化的内容更新到真实DOM树。

*/
import { init } from 'snabbdom/build/package/init'
import { h } from 'snabbdom/build/package/h'

// 如果是webpack5.x的版本打包的话可以直接这么导入snabbdom:import { init } from 'snabbdom/init'
// import { h } from 'snabbdom/h'
// patch 可以把虚拟dom转换为真实DOM;并且挂载到DOM树上
// 有两个参数;都是vnode;第一个参数如果是真实DOM;会转为VNode然后在做比较;然后返回新的VNode
const patch = init([])

// h函数的两个参数:第一个参数(字符串)是标签+选择器;第二个参数如果是字符串的话,代表的是文本内容
// vnode描述的真实DOM
let vnode = h('div#container.cle', 'hello world')
let app = document.querySelector('#app')

// patch函数的第一个参数:旧的VNode,可以是DOM元素;第二个参数:新的VNode;返回值是新的VNode

let oldVnode = patch(app, vnode)


// 
vnode = h('div#container.xxx', 'hello snabbdom')
patch(oldVnode, vnode)
// 清除div中的内容   h('!')生成的就是一个空的注释节点
// patch(oldVnode, h('!'))

diff算法

// 虚拟DOM中的Diff算法:查找两棵树每一个节点的差异
// 执行过程:在对开始和结束节点比较的时候:总共有四种情况:
// ----- oldStartVnode/ newStartVnode(旧开始节点/新开始节点);
// ----- oldEndVnode/ newEndVnode(旧结束节点/新结束节点);
// ----- oldStartVnode/ newEndVnode(旧开始节点/新结束节点);
// ----- oldEndVnode/ newStartVnode(旧结束节点/新开始节点);
// 开始和结束节点:如果新旧开始节点是sameVnode(key和sel相同),调用patchVnode()对比和更新节点,把旧开始和新开始索引往后移动,oldStartIdx++/ newStartIdx++,再去比较第二个节点,把第二个节点作为开始节点开始比较;如果不是sameVnode则会比较最后一个旧结束节点和新结束节点是否是sameVnode;相同则移动到倒数第二个节点作为结束节点进行比较。

// 旧开始节点和新结束节点:比较两个节点;如果是sameVNode就调用patchVnode()对比和更新节点,并且把oldStartVnode对应的DOM元素移动到最后,更新索引,始终保证第一个节点就是旧开始节点,新结束节点往前移动一位,接着进行比较两个节点
// 旧结束节点和新开始节点: 比较两个节点;如果是sameVNode就调用patchVnode()对比和更新节点,并且把oldEndVnode对应的DOM元素移动到最前面,更新索引,始终保证最后一个节点就是旧结束节点,新开始节点往后移动一位,接着进行比较两个节点

// 如果以上四种情况都不满足:说明开始和结束节点都不相同。就需要从旧节点中查找新节点(遍历新节点,查找是否在旧节点中有相同key和sel值得节点;若没有,创建新的DOM元素,并插入到最前面的位置;若有;把找到的旧节点赋值给elmToMove这个变量,调用parchVnode比较两个节点,和更新节点差异,再把elmToMove对于的DOM元素移动到最前面) 。  
// 循环结束:1、当老节点的所有子节点先遍历完,循环结束(oldStartIdx > oldEndIdx) 2、新节点的所有子节点先遍历完,循环结束 (newStartIdx > newEndIdx)



// diff算法过程:
/*
 -- patch(oldVnode,newVnode)
 -- 打补丁,把新节点中变化的内容渲染到真实DOM,最后返回新节点作为下一次处理的旧节点。
 -- 对比新旧VNode是否相同节点(节点的key和sel相同)
 -- 如果不是相同节点,删除之前的内容,重新渲染
 -- 如果是相同节点,在判断新的VNode是否有text,如果有并且和oldVNode的text不同,直接更新文本内容
 -- 如果新的VNode有children,判断子节点是否有变化
 -- diff过程只进行同层级比较,时间复杂度O(n)


*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值