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 才会删除元素。

相关推荐
twins352030 分钟前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky1 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~1 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
安冬的码畜日常1 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
l1x1n02 小时前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html
昨天;明天。今天。2 小时前
案例-任务清单
前端·javascript·css
zqx_73 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己3 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称4 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色4 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript