近几年随着 React、Vue 等前端框架一直衰亡,Virtual DOM 概念也越来越火,被用到越来越多的框架、库中。Virtual DOM 是基于实在 DOM 的一层形象,用简略的 JS 对象形容实在 DOM。本文要介绍的 Snabbdom 就是 Virtual DOM 的一种简略实现,并且 Vue 的 Virtual DOM 也参考了 Snabbdom 实现形式。

对于想要深刻学习 Vue Virtual DOM 的敌人,倡议先学习 Snabbdom,对了解 Vue 会很有帮忙,并且其外围代码 200 多行。

本文筛选 Snabbdom 模块零碎作为次要外围点介绍,其余内容能够查阅官网文档《Snabbdom》。

一、Snabbdom 是什么

Snabbdom 是一个专一于简略性、模块化、弱小个性和性能的虚构 DOM 库。其中有几个外围个性:

  1. 外围代码 200 行,并且提供丰盛的测试用例;
  2. 领有弱小模块零碎,并且反对模块拓展和灵便组合;
  3. 在每个 VNode 和全局模块上,都有丰盛的钩子,能够在 Diff 和 Patch 阶段应用。

接下来从一个简略示例来体验一下 Snabbdom。

1. 疾速上手

装置 Snabbdom:

npm install snabbdom -D

接着新建 index.html,设置入口元素:

<div id="app"></div>

而后新建 demo1.js 文件,并应用 Snabbdom 提供的函数:

// demo1.jsimport { h } from 'snabbdom/src/package/h'import { init } from 'snabbdom/src/package/init'const patch = init([])let vnode = h('div#app', 'Hello Leo')const app = document.getElementById('app')patch(app, vnode)

这样就实现一个简略示例,在浏览器关上 index.html,页面将显示 “Hello Leo” 文本。

接下来,我会以 snabbdom-demo 我的项目作为学习示例,从简略示例到模块零碎应用的示例,深刻学习和剖析 Snabbdom 源码,重点剖析 Snabbdom 模块零碎。

二、Snabbdom-demo 剖析

Snabbdom-demo 我的项目中的三个演示代码,为咱们展现如何从简略到深刻 Snabbdom。
首先克隆仓库并装置:

$ git clone https://github.com/zyycode/snabbdom-demo.git$ npm install

尽管本我的项目没有 README.md 文件,但我的项目目录比拟直观,咱们能够轻松的从 src 目录找到这三个示例代码的文件:

  • 01-basicusage.js
  • 02-basicusage.js
  • 03-modules.js -> 本文外围介绍

接着在 index.html 中引入想要学习的代码文件,默认 <script src="./src/01-basicusage.js"></script>  ,通过 package.json 可知启动命令并启动我的项目:

$ npm run dev

1. 简略示例剖析

当咱们要钻研一个库或框架等比较复杂的我的项目,能够通过官网提供的简略示例代码进行剖析,咱们这里抉择该我的项目中最简略的 01-basicusage.js 代码进行剖析,其代码如下:

// src/01-basicusage.jsimport { h } from 'snabbdom/src/package/h'import { init } from 'snabbdom/src/package/init'const patch = init([])let vnode = h('div#container.cls', 'Hello World')const app = document.getElementById('app') // 入口元素const oldVNode = patch(app, vnode)// 假如时刻vnode = h('div', 'Hello Snabbdom')patch(oldVNode, vnode)

运行我的项目当前,能够看到页面展现了“Hello Snabbdom”文本,这里你会感觉奇怪,后面的 “Hello World” 文本去哪了

起因很简略,咱们把 demo 中的上面两行代码正文后,页面便显示文本是 “Hello World”:

vnode = h('div', 'Hello Snabbdom')patch(oldVNode, vnode)

这里咱们能够猜想 patch() 函数能够将 VNode 渲染到页面。更进一步能够了解为,这边第一个执行 patch() 函数为首次渲染,第二次执行 patch() 函数为更新操作

2. VNode 介绍

这里可能会有小伙伴纳闷,示例中的 VNode 是什么?这里简略解释下:

VNode,该对象用于形容节点的信息,它的全称是虚构节点(virtual node)。与 “虚构节点” 相关联的另一个概念是 “虚构 DOM”,它是咱们对由 Vue 组件树建设起来的整个 VNode 树的称说。“虚构 DOM” 由 VNode 组成的。
—— 全栈修仙之路 《Vue 3.0 进阶之 VNode 探秘》

其实 VNode 就是一个 JS 对象,在 Snabbdom 中是这么定义 VNode 的类型:

export interface VNode {  sel: string | undefined; // selector的缩写  data: VNodeData | undefined; // 上面VNodeData接口的内容  children: Array<VNode | string> | undefined; // 子节点  elm: Node | undefined; // element的缩写,存储了实在的HTMLElement  text: string | undefined; // 如果是文本节点,则存储text  key: Key | undefined; // 节点的key,在做列表时很有用}export interface VNodeData {  props?: Props  attrs?: Attrs  class?: Classes  style?: VNodeStyle  dataset?: Dataset  on?: On  hero?: Hero  attachData?: AttachData  hook?: Hooks  key?: Key  ns?: string // for SVGs  fn?: () => VNode // for thunks  args?: any[] // for thunks  [key: string]: any // for any other 3rd party module}

在 VNode 对象中含形容节点选择器 sel 字段、节点数据 data 字段、节点所蕴含的子节点 children 字段等。

在这个 demo 中,咱们仿佛并没有看到模块零碎相干的代码,没事,因为这是最简略的示例,下一节会具体介绍。

咱们在学习一个函数时,能够重点理解该函数的“入参”和“出参”,大抵就能判断该函数的作用。

从这个 demo 次要执行过程能够看出,次要用到有三个函数: init() / patch() / h() ,它们到底做什么用的呢?咱们剖析一下 Snabbdom 源码中这三个函数的入参和出参状况:

3. init() 函数剖析

init() 函数被定义在 package/init.ts 文件中:

// node_modules/snabbdom/src/package/init.tsexport function init (modules: Array<Partial<Module>>, domApi?: DOMAPI) {    // 省略其余代码}

其参数类型如下:

function init(modules: Array<Partial<Module>>, domApi?: DOMAPI): (oldVnode: VNode | Element, vnode: VNode) => VNodeexport type Module = Partial<{  pre: PreHook  create: CreateHook  update: UpdateHook  destroy: DestroyHook  remove: RemoveHook  post: PostHook}>  export interface DOMAPI {  createElement: (tagName: any) => HTMLElement  createElementNS: (namespaceURI: string, qualifiedName: string) => Element  createTextNode: (text: string) => Text  createComment: (text: string) => Comment  insertBefore: (parentNode: Node, newNode: Node, referenceNode: Node | null) => void  removeChild: (node: Node, child: Node) => void  appendChild: (node: Node, child: Node) => void  parentNode: (node: Node) => Node | null  nextSibling: (node: Node) => Node | null  tagName: (elm: Element) => string  setTextContent: (node: Node, text: string | null) => void  getTextContent: (node: Node) => string | null  isElement: (node: Node) => node is Element  isText: (node: Node) => node is Text  isComment: (node: Node) => node is Comment}

init() 函数接管一个模块数组 modules 和可选的 domApi 对象作为参数,返回一个函数,即 patch() 函数。
domApi 对象的接口蕴含了很多 DOM 操作的办法。
这里的 modules 参数本文将重点介绍。

4. patch() 函数剖析

init() 函数返回了一个 patch() 函数,其类型为:

// node_modules/snabbdom/src/package/init.tspatch(oldVnode: VNode | Element, vnode: VNode) => VNode

patch() 函数接管两个 VNode 对象作为参数,并返回一个新 VNode。

5. h() 函数剖析

h() 函数被定义在 package/h.ts 文件中:

// node_modules/snabbdom/src/package/h.tsexport function h(sel: string): VNodeexport function h(sel: string, data: VNodeData | null): VNodeexport function h(sel: string, children: VNodeChildren): VNodeexport function h(sel: string, data: VNodeData | null, children: VNodeChildren): VNodeexport function h (sel: any, b?: any, c?: any): VNode{    // 省略其余代码}

h() 函数接管多种参数,其中必须有一个 sel 参数,作用是将节点内容挂载到该容器中,并返回一个新 VNode。

6. 小结

通过后面介绍,咱们在回过头看看这个 demo 的代码,大抵调用流程如下:

三、深刻 Snabbdom 模块零碎

学习完后面这些基础知识后,咱们曾经晓得 Snabbdom 应用形式,并且晓得其中三个外围办法入参出参状况和大抵作用,接下来开始看本文外围 Snabbdom 模块零碎。

1. Modules 介绍

Snabbdom 模块零碎是 Snabbdom 提供的一套可拓展可灵便组合的模块零碎,用来为 Snabbdom 提供操作 VNode 时的各种模块反对,如咱们组建须要解决 style 则引入对应的 styleModule,须要处理事件,则引入 eventListenersModule 既可,这样就达到灵便组合,能够反对按需引入的成果。

Snabbdom 模块零碎的特点能够概括为:反对按需引入、独立治理、职责繁多、不便组合复用、可维护性强。

当然 Snabbdom 模块零碎还有其余内置模块:

模块名称模块性能示例代码
attributesModule为 DOM 元素设置属性,在属性增加和更新时应用 setAttribute 办法。h('a', { attrs: { href: '/foo' } }, 'Go to Foo')
classModule用来动静设置和切换 DOM 元素上的 class 名称。h('a', { class: { active: true, selected: false } }, 'Toggle')
datasetModule为 DOM 元素设置自定义数据属性(data- *)。而后能够应用 HTMLElement.dataset 属性拜访它们。h('button', { dataset: { action: 'reset' } }, 'Reset')
eventListenersModule为 DOM 元素绑定事件监听器。h('div', { on: { click: clickHandler } })
propsModule为 DOM 元素设置属性,如果同时应用 attributesModule,则会被 attributesModule 笼罩。h('a', { props: { href: '/foo' } }, 'Go to Foo')
styleModule为 DOM 元素设置 CSS 属性。h('span', {style: { color: '#c0ffee'}}, 'Say my name')

2. Hooks 介绍

Hooks 也称钩子,是 DOM 节点生命周期的一种办法。Snabbdom 提供丰盛的钩子抉择。模块既应用钩子来扩大 Snabbdom,也在一般代码中应用钩子,用来在 DOM 节点生命周期中执行任意代码。

这里大抵介绍一下所有的 Hooks:

钩子名称触发机会回调参数
prepatch 阶段开始。none
init已增加一个 VNode。vnode
create基于 VNode 创立了一个 DOM 元素。emptyVnode, vnode
insert一个元素已增加到 DOM 元素中。vnode
prepatch一个元素行将进入 patch 阶段。oldVnode, vnode
update一个元素开始更新。oldVnode, vnode
postpatch一个元素实现 patch 阶段。oldVnode, vnode
destroy一个元素间接或间接被删除。vnode
remove一个元素间接从 DOM 元素中删除。vnode, removeCallback
postpatch 阶段完结。none

模块中能够应用这些钩子:precreateupdatedestroyremovepost
单个元素能够应用这些钩子:initcreateinsertprepatchupdatepostpatchdestroyremove

Snabbdom 是这么定义钩子的:

// snabbdom/src/package/hooks.tsexport type PreHook = () => anyexport type InitHook = (vNode: VNode) => anyexport type CreateHook = (emptyVNode: VNode, vNode: VNode) => anyexport type InsertHook = (vNode: VNode) => anyexport type PrePatchHook = (oldVNode: VNode, vNode: VNode) => anyexport type UpdateHook = (oldVNode: VNode, vNode: VNode) => anyexport type PostPatchHook = (oldVNode: VNode, vNode: VNode) => anyexport type DestroyHook = (vNode: VNode) => anyexport type RemoveHook = (vNode: VNode, removeCallback: () => void) => anyexport type PostHook = () => anyexport interface Hooks {  pre?: PreHook  init?: InitHook  create?: CreateHook  insert?: InsertHook  prepatch?: PrePatchHook  update?: UpdateHook  postpatch?: PostPatchHook  destroy?: DestroyHook  remove?: RemoveHook  post?: PostHook}

接下来咱们通过 03-modules.js 文件的示例代码,咱们须要款式解决事件操作,因而引入这两个模块,并进行灵便组合

// src/03-modules.jsimport { h } from 'snabbdom/src/package/h'import { init } from 'snabbdom/src/package/init'// 1. 导入模块import { styleModule } from 'snabbdom/src/package/modules/style'import { eventListenersModule } from 'snabbdom/src/package/modules/eventlisteners'// 2. 注册模块const patch = init([ styleModule, eventListenersModule ])// 3. 应用 h() 函数的第二个参数传入模块须要的数据(对象)let vnode = h('div', {  style: { backgroundColor: '#4fc08d', color: '#35495d' },  on: { click: eventHandler }}, [  h('h1', 'Hello Snabbdom'),  h('p', 'This is p tag')])function eventHandler() {  console.log('clicked.')}const app = document.getElementById('app')patch(app, vnode)

下面代码中,引入了 styleModule 和 eventListenersModule 两个模块,并且作为参数组合,传入 init() 函数中。
此时咱们能够看到页面上显示的内容曾经有蕴含款式,并且点击事件也能失常输入日志 'clicked.'

这里咱们看下 styleModule 模块源码,把代码精简一下:

// snabbdom/src/package/modules/style.tsfunction updateStyle (oldVnode: VNode, vnode: VNode): void {    // 省略其余代码}function forceReflow () {  // 省略其余代码}function applyDestroyStyle (vnode: VNode): void {  // 省略其余代码}function applyRemoveStyle (vnode: VNode, rm: () => void): void {  // 省略其余代码}export const styleModule: Module = {  pre: forceReflow,  create: updateStyle,  update: updateStyle,  destroy: applyDestroyStyle,  remove: applyRemoveStyle}

在看看 eventListenersModule 模块源码:

// snabbdom/src/package/modules/eventlisteners.tsfunction updateEventListeners (oldVnode: VNode, vnode?: VNode): void {    // 省略其余代码}export const eventListenersModule: Module = {  create: updateEventListeners,  update: updateEventListeners,  destroy: updateEventListeners}

显著能够看出,两个模块返回的都是个对象,并且每个属性为一种钩子,如 pre/create 等,值为对应的处理函数,每个处理函数有对立的入参。

持续看下 styleModule 中,款式是如何绑定下来的。这里剖析它的 updateStyle 办法,因为元素创立(create 钩子)和元素更新(update 钩子)阶段都是通过这个办法解决:

// snabbdom/src/package/modules/style.tsfunction updateStyle (oldVnode: VNode, vnode: VNode): void {  var cur: any  var name: string  var elm = vnode.elm  var oldStyle = (oldVnode.data as VNodeData).style  var style = (vnode.data as VNodeData).style  if (!oldStyle && !style) return  if (oldStyle === style) return    // 1. 设置新旧 style 默认值  oldStyle = oldStyle || {}  style = style || {}  var oldHasDel = 'delayed' in oldStyle  // 2. 比拟新旧 style  for (name in oldStyle) {    if (!style[name]) {      if (name[0] === '-' && name[1] === '-') {        (elm as any).style.removeProperty(name)      } else {        (elm as any).style[name] = ''      }    }  }  for (name in style) {    cur = style[name]    if (name === 'delayed' && style.delayed) {      // 省略局部代码    } else if (name !== 'remove' && cur !== oldStyle[name]) {      if (name[0] === '-' && name[1] === '-') {        (elm as any).style.setProperty(name, cur)      } else {        // 3. 设置新 style 到元素        (elm as any).style[name] = cur      }    }  }}

3. init() 剖析

接着咱们看下 init() 函数外部如何解决这些 Module。

首先在 init.ts 文件中,能够看到申明了默认反对的 Hooks 钩子列表:

// snabbdom/src/package/init.tsconst hooks: Array<keyof Module> = ['create', 'update', 'remove', 'destroy', 'pre', 'post']

接着看 hooks 是如何应用的:

// snabbdom/src/package/init.tsexport function init (modules: Array<Partial<Module>>, domApi?: DOMAPI) {  let i: number  let j: number  const cbs: ModuleHooks = {  // 创立 cbs 对象,用于收集 module 中的 hook    create: [],    update: [],    remove: [],    destroy: [],    pre: [],    post: []  }    // 收集 module 中的 hook,并保留在 cbs 中  for (i = 0; i < hooks.length; ++i) {    cbs[hooks[i]] = []    for (j = 0; j < modules.length; ++j) {      const hook = modules[j][hooks[i]]      if (hook !== undefined) {        (cbs[hooks[i]] as any[]).push(hook)      }    }  }    // 省略其余代码,稍后介绍}

下面代码中,创立 hooks 变量用来申明默认反对的 Hooks 钩子,在 init() 函数中,创立 cbs 对象,通过两层循环,保留每个 module 中的 hook 函数到 cbs 对象的指定钩子中。

通过断点能够看到这是 demo 中,cbs 对象是上面这个样子:

这里 cbs 对象收集了每个 module 中的 Hooks 处理函数,保留到对应 Hooks 数组中。比方这里的 create 钩子中保留了 updateStyle 函数和 updateEventListeners 函数。

到这里, init() 函数曾经保留好所有 module 的 Hooks 处理函数,接下来就要看看 init() 函数返回的 patch() 函数,这外面将用到后面保留好的 cbs 对象。

4. patch() 剖析

init() 函数中最终返回一个 patch() 函数,这边造成一个闭包,闭包外面能够应用到 init() 函数作用域定义的变量和办法,因而在 patch() 函数中能应用 cbs 对象。

patch() 函数会在不同机会点(能够参照后面的 Hooks 介绍),遍历 cbs 对象中不同 Hooks 处理函数列表。

// snabbdom/src/package/init.tsexport function init (modules: Array<Partial<Module>>, domApi?: DOMAPI) {    // 省略其余代码  return function patch (oldVnode: VNode | Element, vnode: VNode): VNode {    let i: number, elm: Node, parent: Node    const insertedVnodeQueue: VNodeQueue = []    for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]()  // [Hooks]遍历 pre Hooks 处理函数列表    if (!isVnode(oldVnode)) {      oldVnode = emptyNodeAt(oldVnode) // 当 oldVnode 参数不是 VNode 则创立一个空的 VNode    }    if (sameVnode(oldVnode, vnode)) {  // 当两个 VNode 为同一个 VNode,则进行比拟和更新      patchVnode(oldVnode, vnode, insertedVnodeQueue)    } else {      createElm(vnode, insertedVnodeQueue) // 当两个 VNode 不同,则创立新元素      if (parent !== null) {  // 当该 oldVnode 有父节点,则插入该节点,而后移除原来节点        api.insertBefore(parent, vnode.elm!, api.nextSibling(elm))        removeVnodes(parent, [oldVnode], 0, 0)      }    }    for (i = 0; i < cbs.post.length; ++i) cbs.post[i]()  // [Hooks]遍历 post Hooks 处理函数列表    return vnode  }}

patchVnode() 函数定义如下:

  function patchVnode (oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {    // 省略其余代码    if (vnode.data !== undefined) {      for (let i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)  // [Hooks]遍历 update Hooks 处理函数列表    }  }

createVnode() 函数定义如下:

  function createElm (vnode: VNode, insertedVnodeQueue: VNodeQueue): Node {    // 省略其余代码    const sel = vnode.sel    if (sel === '!') {      // 省略其余代码    } else if (sel !== undefined) {      for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode)  // [Hooks]遍历 create Hooks 处理函数列表      const hook = vnode.data!.hook    }    return vnode.elm  }

removeNodes() 函数定义如下:

  function removeVnodes (parentElm: Node,vnodes: VNode[],startIdx: number,endIdx: number): void {    // 省略其余代码    for (; startIdx <= endIdx; ++startIdx) {      const ch = vnodes[startIdx]      if (ch != null) {        rm = createRmCb(ch.elm!, listeners)        for (let i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm) // [Hooks]遍历 remove Hooks 处理函数列表      }    }  }

这部分代码跳转较多,总结一下这个过程,如下图:

四、自定义 Snabbdom 模块

后面咱们介绍了 Snabbdom 模块零碎是如何收集 Hooks 并保留下来,而后在不同机会点执行不同的 Hooks。

在 Snabbdom 中,所有模块独立在 src/package/modules 下,应用的时候能够灵便组合,也不便做解耦和跨平台,并且所有 Module 返回的对象中每个 Hooks 类型如下:

// snabbdom/src/package/init.tsexport type Module = Partial<{  pre: PreHook  create: CreateHook  update: UpdateHook  destroy: DestroyHook  remove: RemoveHook  post: PostHook}>// snabbdom/src/package/hooks.tsexport type PreHook = () => anyexport type CreateHook = (emptyVNode: VNode, vNode: VNode) => anyexport type UpdateHook = (oldVNode: VNode, vNode: VNode) => anyexport type DestroyHook = (vNode: VNode) => anyexport type RemoveHook = (vNode: VNode, removeCallback: () => void) => anyexport type PostHook = () => any

因而,如果开发者须要自定义模块,只需实现不同 Hooks 并导出即可。

接下来咱们实现一个简略的模块 replaceTagModule,用来将节点文本主动过滤掉 HTML 标签

1. 初始化代码

思考到不便调试,咱们间接在 node_modules/snabbdom/src/package/modules/ 目录中新建 replaceTag.ts 文件,而后写个最简略的 demo 框架:

import { VNode, VNodeData } from '../vnode'import { Module } from './module'const replaceTagPre = () => {    console.log("run replaceTagPre!")}const updateReplaceTag = (oldVnode: VNode, vnode: VNode): void => {    console.log("run updateReplaceTag!", oldVnode, vnode)}const removeReplaceTag = (vnode: VNode): void => {    console.log("run removeReplaceTag!", vnode)}export const replaceTagModule: Module = {    pre: replaceTagPre,    create: updateReplaceTag,    update: updateReplaceTag,    remove: removeReplaceTag}

接下来引入到 03-modules.js 代码中,并简化下代码:

import { h } from 'snabbdom/src/package/h'import { init } from 'snabbdom/src/package/init'// 1. 导入模块import { styleModule } from 'snabbdom/src/package/modules/style'import { eventListenersModule } from 'snabbdom/src/package/modules/eventlisteners'import { replaceTagModule } from 'snabbdom/src/package/modules/replaceTag';// 2. 注册模块const patch = init([  styleModule,  eventListenersModule,  replaceTagModule])// 3. 应用 h() 函数的第二个参数传入模块须要的数据(对象)let vnode = h('div', '<h1>Hello Leo</h1>')const app = document.getElementById('app')const oldVNode = patch(app, vnode)let newVNode = h('div', '<div>Hello Leo</div>')patch(oldVNode, newVNode)

刷新浏览器,就能够看到 replaceTagModule 的每个钩子都被失常执行:

2. 实现 updateReplaceTag() 函数

咱们删除掉多余代码,接下来实现 updateReplaceTag() 函数,当 vnode 创立和更新时,都会调用该办法。

import { VNode, VNodeData } from '../vnode'import { Module } from './module'const regFunction = str => str && str.replace(/\<|\>|\//g, "");const updateReplaceTag = (oldVnode: VNode, vnode: VNode): void => {    const oldVnodeReplace = regFunction(oldVnode.text);    const vnodeReplace = regFunction(vnode.text);    if(oldVnodeReplace === vnodeReplace) return;    vnode.text = vnodeReplace;}export const replaceTagModule: Module = {    create: updateReplaceTag,    update: updateReplaceTag,}  

updateReplaceTag() 函数中,比拟新旧 vnode 的文本内容是否统一,如果统一则间接返回,否则将新的 vnode 的替换后的文本设置到 vnode 的 text 属性,实现更新。

其中有个细节:

vnode.text = vnodeReplace;

这里间接对 vnode.text 进行赋值,页面上的内容也随之发生变化。这是因为 vnode 是个响应式对象,通过调用其 setter 办法,会触发响应式更新,这样就实现页面内容更新。

于是咱们看到页面内容中的 HTML 标签被清空了。

3. 小结

这个大节中,咱们实现一个简略的 replaceTagModule 模块,体验了一下 Snabbdom 模块灵便组合的特点,当咱们须要自定义某些模块时,便能够依照 Snabbdom 的模块开发方式,开发自定义模块,而后通过 Snabbdom 的 init() 函数注入模块即可。

咱们再回顾一下 Snabbdom 模块零碎特点:反对按需引入、独立治理、职责繁多、不便组合复用、可维护性强。

五、通用模块生命周期模型

上面我将后面 Snabbdom 的模块零碎,形象为一个通用模块生命周期模型,其中蕴含三个核心层:

  1. 模块定义层

在本层能够依照模块开发标准,自定义各种模块。

  1. 模块应用层

个别是在业务开发层或组件层中,用来导入模块。

  1. 模块初始化层

个别是在开发的模块零碎的插件中,提供初始化函数(init 函数),执行初始化函数会遍历每个 Hooks,并执行对应处理函数列表的每个函数。

形象后的模型如下:

在应用 Module 的时候就能够灵便组合搭配应用啦,在模块初始化层,就会做好调用。

六、总结

本文次要以 Snabbdom-demo 仓库为学习示例,学习了 Snabbdom 运行流程和 Snabbdom 模块零碎的运行流程,还通过手写一个简略的 Snabbdom 模块,带大家领略一下 Snabbdom 模块的魅力,最初为大家总结了一个通用模块插件模型。

大家好好把握 Snabbdom 对了解 Vue 会很有帮忙。