以前有简单看过 Vue patch 部分的源码,了解了是基于 Snabbdom 库实现的。最近想详细了解下 Vue 处理 vnode patch 的整个过程,想知道它在 Snabbdom 之上作了哪些事情?因此带着这个问题,写了这篇文章来记录。html
A virtual DOM library with focus on simplicity, modularity, powerful features and performance. (一个虚拟 DOM 库,专一于简单性,模块化,强大的功能和性能。)vue
Snabbdom 核心代码大约只有 200 行。它提供了模块化架构,具备丰富的功能,可经过自定义模块进行扩展。在了解核心 patch 前,须要先了解 snabbdom 的模块化架构思想。node
在节点的生命周期里作一些任务,来扩展 Snabbdom ,就能够称之为模块。 Snabbdom 在 patch 的过程当中会注入不少钩子(hooks)。模块实现就是基于这些钩子,钩子能够理解为 vnode 节点的生命周期。git
好比 eventlisteners 模块:github
Hooks
是一种挂载 vnode 生命周期的方式。软件开发领域有相似这样的设计思想,好比版本管理工具 git
。Snabbdom 中的模块就是基于此来实现扩展的。固然,也能够传递 hook 配置,实如今 vnode 生命周期里作一些事(这种只针对单个节点,而模块针对全部节点),例如:web
h('div.row', {
key: movie.rank,
hook: {
insert: vnode => {
movie.elmHeight = vnode.elm.offsetHeight
}
}
})
复制代码
Vue 中的指令就是基于此实现的。具体的生命周期能够参考官方文档:github.com/snabbdom/sn…数组
Snabbdom 的核心部分,patch 函数由 snabbdom.init
建立,初始化时提供绑定 hook 的模块。 若是 oldVnode
是具备父节点的 DOM 元素,则 newVnode
将变为 DOM 节点,而且传递的元素将被建立的 DOM 节点替换。若是传递的是个 vnode
实例,则会比对此节点和递归对比它的子节点,并作 dom 更新。这一块网上的源码解析文章也比较多,这里就很少介绍了。性能优化
h
函数用来建立 vnode 实例,类比 Vue render 函数中的参数 h
,Vue 中扩展了入参。好比第一个参数 tag
,Vue 能够是对象或函数;第二个参数 data
,Vue 增长了一些特有的功能好比:scopedSlots
, slot
, directive
, ref
... 等。架构
Vue 中有组件的概念,组件一样是能够嵌套的,全部就存在父组件和子组件。父组件 render 函数执行后,子组件并无被初始化,仅仅是建立了一个特殊的 vnode 节点,而这个节点上会绑定一些 hooks: init
, prepatch
, insert
, destroy
。当父组件 patch 执行的过程当中,遇到子组件的话,会调用 hook: init 方法,此方法中才会初始化组件实例。后续 insert hook 触发时,相应的会调用组件 mounted
生命周期钩子。这一块源码能够查看 github.com/vuejs/vue/b…dom
setScope
也是在 patch 执行过程当中调用的,它会往 dom 节点上增长 ${scopedId}
属性,用来实现 scoped
样式功能。
patch 过程当中会更新 ref
到上下文实例上。
由于有组件概念,因此 Vue 中建立 vnode 第一个参数 tag 能够是对象或函数,Snabbdom 上只能是字符串。
pre
和 post
2 个钩子,这 2 个钩子分别用来在 patch 执行先后调用的init
钩子只对组件节点生效,而 Snabbdom 中全部节点均可定义 init
hooksactive
钩子,用来处理 <keep-alive>
嵌套 <transition>
组件产生的边界问题Vue 会对静态节点作性能优化。
staticRenderFns
属性里。除了纯 html 语法产生的静态节点外,v-once
, v-pre
也会产生静态节点。_staticTrees
数组中。组件更新从新触发 render 时,不会从新建立 vnode 节点,直接使用以前已有的静态节点。进而不会触发 patchVnode
操做。对于 Vue 服务端渲染输出的 html,客户端初始化挂载节点时,会把已经渲染好的 dom 和 vnode 一一绑定,以达到同构的效果,hydrate
函数就作了这个任务。
我整理了一份比较详细的 patch
流程图。 一些细节没有写入,好比:updateChildren
diff 过程、异步组件、keep-alive、hydrate...
Vue 2 中的指令就是基于 hooks 实现的,从 directive 的生命周期来看:
vnode 建立、更新、销毁时都会更新所在组件上的 $refs
属性。 patch 过程当中处理了子组件为空时,父组件指向的 ref 为 undefined 的边界状况
对于动态绑定 style,Vue 还会智能的往不支持的属性前加厂商前缀。
const normalize = cached(function(prop) {
emptyStyle = emptyStyle || document.createElement('div').style
prop = camelize(prop)
if (prop !== 'filter' && prop in emptyStyle) {
return prop
}
const capName = prop.charAt(0).toUpperCase() + prop.slice(1)
for (let i = 0; i < vendorNames.length; i++) {
const name = vendorNames[i] + capName
if (name in emptyStyle) {
return name
}
}
})
复制代码
Vue 还支持 value 数组写法。好比 {display: ["-webkit-box", "-ms-flexbox", "flex"]}
Snabbdom 中对 style 增长了 hooks: remove,这个是为了实现节点被移除时的过渡动效,而 Vue 对过渡动效的处理封装在了 <transition>
组件中。
其实 Vue 中还有不少功能都依赖 vnode 节点 patch 的过程,transition
的功能也比较多,这里暂时不深刻了。 因为本人理解有限,文中若有任何问题欢迎留言指正。