You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
import { init } from './snabbdom';
import { attributesModule } from './modules/attributes'; // for setting attributes on DOM elements
import { classModule } from './modules/class'; // makes it easy to toggle classes
import { propsModule } from './modules/props'; // for setting properties on DOM elements
import { styleModule } from './modules/style'; // handles styling on elements with support for animations
import { eventListenersModule } from './modules/eventlisteners'; // attaches event listeners
import { h } from './h'; // helper function for creating vnodes
var patch = init([
attributesModule,
classModule,
propsModule,
styleModule,
eventListenersModule
]);
export default { h, patch };
可以看到,入口仅仅是初始化得到patch,并且对外暴露出h和patch。看到这里疑问来了:
h是干嘛用的?
init传入模块的目的?得到的patch是干嘛用的?
好,让我们抽丝剥茧,一个个看。
1. h是干嘛用的?
从src/h.js可以看到:
export function h(sel, b, c) {
//......
// 上面仅仅是对h参数进行优化处理,得到vnode需要的入参,最终返回vnode的调用结果,即生成js虚拟node
return vnode(sel, data, children, text, undefined);
}
简介
读vue源码的时候,发现它的虚拟dom思想是参考的snabbdom,故先研读snabbdom还是比较的有价值。因为snabbdom就是一个纯实现虚拟dom的库,代码非常的优雅和纯粹,对于研究虚拟dom思想再适合不过了。
代码目录
snabbdom官方代码是使用的typescript+flow写的,个人更喜欢es6的写法,故将其转成了snabbdom-es6,并使用rollup整合打包用于解读。
目录如下(跟官方基本一致):
Virtual Dom实现思路
一般而言,实现一个Virtual Dom基本是如下的三个步骤,具体可以参考实现一个virtual dom:
那么snabbdom是否也是一样遵循这个思路呢?
先透露下答案:大体一致,不过后面两个步骤是合在了一起,即找到新旧节点差异后,立马应用到实际的dom节点上了。
接下来就一步步分析其具体实现。
初始化和对外暴露的入口 index.js
接下来我们就从入口开始解读:
可以看到,入口仅仅是初始化得到patch,并且对外暴露出h和patch。看到这里疑问来了:
好,让我们抽丝剥茧,一个个看。
1. h是干嘛用的?
从src/h.js可以看到:
可以很明显看到,h只是对vnode的再封装,目的在于将传参进一步处理提供给vnode使用。最后返回vnode,即创建js虚拟节点。
而vnode也很简单:
仅仅是使用一个对象,用于描述DOM节点的结构。正常来说,描述一个节点只需要 tagName、props、children就够了。那这里其他的参数是干嘛用的呢?
2. init传入模块的目的?得到的patch是干嘛用的?
snabbdom.js整体结构
可以看到,snabbdom.js返回了一个init函数,init函数初始化了cbs和一些函数,直接返回了patch函数。那么为什么不直接返回patch函数,然后再它里面初始化cbs呢?
答案是为了更加灵活的处理,即cbs的处理可能根据需要传入的modules进行初始化,然后patch相当于一个闭包,引用了闭包环境中的cbs和相关函数。
hooks初始化
这里在init时初始化钩子函数到cbs对象中,后续就可以在不同阶段执行响应的hook钩子了。
实际上,当你看完整个snabbdom代码时,你会发现并没有显示的代码来对dom节点的class、style、attribute等进行处理。其奥秘就在于shabbdom在各个主要的环节提供了钩子,通过它执行扩展模块,attribute、props、eventlistener等都是通过扩展模块实现的。
如上面讲各个扩展模块的属性预存到了cbs上,在不同的环节上执行对应的钩子,就完成了对这些模块的扩展。
如src/modules/class.js的classModule:
扩展了class的crete和update方法,在patchNode中有这么一段:
即当新节点中data设置了数据(这时候可能就是attribute、class、eventlisteners等变化了)时,直接执行了cbs的所有update钩子,只要有变化,直接就应用了变化做更新处理。
patch函数
逻辑比较简单,主要是判断新旧两棵树是否是同一颗,是的话则进行比对异同patchVnode;不是的话则创建新的树来替换旧树。
patchVnode函数
重点看看patchVnode函数的处理。
这里也不多说了,注释比较清楚了。强调一点:这里可以看出vnode声明时把children和text进行区分的好处了,而且一个Vnode中children和text是二选一的。因为对于dom来说,text也是children,而在虚拟dom里区分开,就可以直接根据它是否存在就行比对了。
上面比对新旧孩纸的逻辑updateChildren是我们需要最最关注,以及最难点的了。下面最核心解读下它。
updateChildren
代码注释已经比较清楚了,不过还是不够清晰的说明整个孩纸列表的比对过程。
下面通过一个比对的例子和图示来进一步说明。
updateChildren示例
以下面两颗新旧节点树为示例进行说明:
整合成children对比状态:
1.round-1: 边界情况都不满足,调用createKeyToOldIdx创建key-index映射对象,判断newStartVnode是否在oldCh中间。很明显存在,对应上面的 condition 11,实际的dom需要把B移动到A前面,旧虚拟Dom里需要把B置为undefined:
2.round-2: 很明显,当前的oldStartVnode和newStartVnode相同,对应上面的condition 5。直接比对差异,oldStartIdx和newStartIdx各加1:
3.round-3: oldStartVnode为null,对应condition 1,oldStartIdx加一位:
4.round-4: 和2一样,得到结果:
5.rond-5: 同4,得到结果:
6.很明显,旧节点先遍历完了,对应上面的 condition 12,添加剩下的所有未遍历到的新节点。
至此,代码注释加上图例,应该很清楚的说明了整个updateChildren的过程了。
参考
list-diff
深度剖析:如何实现一个 Virtual DOM 算法
simple-virtual-dom
深入剖析:Vue核心之虚拟DOM
snabbdom源码分析
解析 snabbdom 源码,教你实现精简的 Virtual DOM 库
Virtual DOM 的内部工作原理
The text was updated successfully, but these errors were encountered: