Snabbdom 源码 - patch

案例调试

Snabbdom 的使用 这一篇中,安装了 parcel 打包工具,在使用 npm run dev 的时候会在 dist 目录下生成对应的 .js 文件和 .js.map 文件。.js 文件就是我们打包之后的文件,同名的 .js.map 文件是记录打包之后的文件和源码文件之间的关系,目的是方便调试。

先来看一下当前源码里的代码:

js 复制代码
const patch = init([])
let vnode = h('div#container.cls', 'Hello World')
let app = document.querySelector('#app')
patch(app, vnode)

我们在浏览器上打开控制台,查看 source 栏下的内容,可以看到打包后的文件,在 src 下的文件是我们的源码文件。

patch

我们在源码的 js 文件的 patch 方法处打一个断点,查看一下 patch 的实现。

  1. f11 进入 patch 方法,继续按 f11 单步执行
  2. 由于我们 init 中没有传入模块,所以 cbs 中没有任何钩子函数
  3. 此时 vnode 不是一个 vnode 对象(dom对象没有sel属性),而是页面上的 #app 元素
  4. 调用 emptyNodeAt 转换成 vnode 对象:
js 复制代码
{ "sel": "div#app", "data": {}, "children": [], "elm": {} }
  1. 判断新旧节点是否为相同节点(keysel 是否相同),当前新旧节点不是,新节点为:
js 复制代码
vnode为:{ "sel": "div#container.cls", "data": {}, "text": "Hello World" }
  1. createElm 后为 vnode 添加了 key 和 children 属性为 undefined,添加了 elm 属性为 真实DOM
  2. 创建新节点的 dom 并插入,插入到旧元素的后面
  3. 移除旧节点的 dom,返回 vnode

createElm

vnode 节点转换成对应的 dom 元素,并将 dom 元素存储到 vnode 节点的 elm 属性当中,在调用 insertBefore 方法的时候才将 vnode 节点的 elm 属性插入到 DOM 树中。

为了更好的调试 createElm 方法,我们修改一下测试案例:

js 复制代码
const patch = init([])

let vnode = h('div#container.cls', {
    // data.hook
    hook: {
        // init: 创建 DOM 之前执行的,其中获取不到 vnode 对应的 DOM 元素
        init (vnode) { console.log(vnode.elm) },
        // create:创建 DOM 完成之后执行的,其中可以获取 vnode 对应的 DOM 元素
        create (emptyNode, vnode) { console.log(vnode.elm) }
    }
}, 'Hello World')
let app = document.querySelector('#app')
patch(app, vnode)

createElm 的流程

  • 触发用户设置的 init 钩子函数
  • 获取 vnodesel 选择器,根据选择器不同执行不同的操作
  1. sel 为 '!',创建注释节点
  2. selundefined,创建文本节点
  3. sel 不为 '!' 或 undefined,创建对应的元素节点
  • 返回新创建的 Dom 元素

创建对应的元素节点

  • 解析 sel ,获取其中包含的 tag/id/class
  • 调用 createElement 方法创建相同 tagDom 元素 elm,并添加到 vnode.elm 属性上
  • elm 添加 idcalss
  • 触发模块中的 create 钩子函数,init 方法中传入的模块
  • 如果 vnode 有子节点,则递归调用 createElm 创建子节点对应的 Dom 元素,并追加到 elm
  • 否则如果 vnodetext 则创建文本节点,并追加到 elm
  • 触发用户设置的 create 钩子函数
  • 如果用户设置了 insert 钩子函数,会将其推入 insertedVnodeQueue 队列中,等 Dom 元素插入到 Dom 树之后执行

removeVnodes

removeVnodes 接收4个参数:父元素,要删除元素对应的 vnode 数组,要删除节点的开始和结束位置,此案例中就是要删除 parent 父元素中的 oldVnode

js 复制代码
removeVnodes(parent, [oldVnode], 0, 0)
  1. 首先根据 vnode 是否有 sel 属性,没有 sel 的认为是文本节点,是文本节点直接删除
  2. 此案例中的 seldiv#wrapper,为元素节点:
  • 触发 vnodedestroy 钩子函数
  • 调用 createRmCb 函数获取删除 DOM 元素的函数 rm
  • 调用模块 cbs 中的 remove 钩子函数,传入 rm
  • 如果用户传入了 remove 钩子函数,则在 remove 钩子函数中需要手动调用 rm
  • 如果用户没有传入 remove 钩子函数,则直接调用 rm

listeners

  1. 为了防止重复删除 DOM 元素,需要给 createRmCb 传递 listeners,值为 cbs.remove.length + 1
  2. 每执行一次 rm,先执行 --listeners,直到 listeners 为 0 才正在的调用 removeChild 删除元素
js 复制代码
   function rm () {
      if (--listeners === 0) {
        ...
        api.removeChild(parent, childElm)
      }
    }

也就是当在模块的 remove 钩子函数中执行 rm 的时候都不会真正删除节点,等执行完全部模块的 remove 钩子函数之后执行的 rm 才会删除元素。

相关推荐
傻瓜搬砖人8 分钟前
SpringMVC的请求
java·前端·javascript·spring
爱上好庆祝23 分钟前
学习js的第六天(js基础的结束)
开发语言·前端·javascript·学习·ecmascript
IT_陈寒33 分钟前
JavaScript的异步地狱,我差点没爬出来
前端·人工智能·后端
光影少年33 分钟前
Webpack打包性能优化方面的经验
前端·webpack·性能优化
Das140 分钟前
通过命令行下载kaggle数据
前端·chrome
剑神一笑1 小时前
CSS Animation Timeline 可视化动画编辑器:从关键帧到流畅动画
前端·css·编辑器
Dylan的码园1 小时前
springBoot与Web后端基础
前端·spring boot·后端
广州华水科技1 小时前
单北斗变形监测应用于水库的精准GNSS技术解析
前端
2401_878454532 小时前
HTML和CSS的复习2
前端·css·html
We་ct2 小时前
吃透现代CSS全技术体系
前端·css·css3·sass·postcss·预处理器