watcher.update实际就是调用updateCompontent
文件路径:core/instance/lifecycle.js
updateCompontent = () => {
vm._update(vm._render(),hydrating)
}
说白了render函数就是把节点转虚拟dom传给update
先执行的是_render函数,在初始化方法renderMixin()里面
_render里面调用的是 createElement()
文件路径:vdom/create-element.js
判断是否保留标签 如div p 直接创建虚拟节点
如果是组件 根据组件的构造函数得到虚拟节点
返回虚拟节点
update方法就是把虚拟dom转为真实dom,里面会有一系列的比较旧虚拟dom和新的虚拟dom
然后执行_update方法,在初始化方法lifecycleMixin()里面
流程:先获取旧的虚拟dom
1如果存在的话就尝试去比对
2不存在的话就证明是首次渲染,走初始化方法patch方法,首次渲染传入真实节点
patch:createPatchFunction({nodeOps,modules})函数做了跨平台处理
nodeOps:节点操作,增删节点等dom操作
modules:modules操作(节点上属性),改变属性等dom操作
createPatchFunction函数,虚拟dom的patching算法基于snabbdom
是工厂函数,返回patch函数
传入老的虚拟节点和新的虚拟节点
1.先判断新节点不存在:删
2.老节点不存在,新的存在:增
3.剩下的情况,(一)首次创建:由于初始化传入的老节点是真实的dom,把老节点转虚拟dom,创建新的节点拼接在旧节点的后面(把vnode转为真实dom),然后把旧节点删除
(二)两个完全相同的节点,要进行打补丁操作(最关键):
静态节点直接跳过,节约性能(标签里面没有变量)
属性更新
新老节点是同一个节点:
1.新旧节点都有孩子节点,执行updateChildren()直接更新两个children数组,重排操作里面很复杂(下面详细说)
2.新节点有孩子节点,老节点里面没有孩子节点(增),先清空文本,然后后增加孩子节点。
c.老节点有孩子节点,新节点没有孩子节点(删),删除孩子节点
4.老节点有文本(没有孩子节点),新节点没有文本,清掉文本。
5.都有文本,文本替换
updateChildren如何比孩子节点
创建四个游标,分别标记老节点children的开始节点和结束节点
新节点children的开始节点和结束节点
开始一共有四种对比方式:
老开始节点跟新开始节点
老结束节点跟新结束节点
老开始节点跟新结束节点
老结束节点跟新开始节点
image.png
旧开始节点和新开始节点相同,直接打补丁操作,游标向下移一位
image.png
旧开始节点和新结束节点相同,把老开始节点移动到老结束节点,然后执行打补丁操作,老开始节点游标向右移一位,新结束节点游标向前移一位
image.png
老结束节点与新开始节点相同,把老结束节点放到第一,打补丁,老结束节点游标向前移一位,新开始节点游标向右移一位
image.png
如果首尾节点没有一样的,用新开始节点去遍历旧节点数组,找到相同的话,把旧节点移动到第一位,再打补丁
image.png
新开始节点在老节点数组没有找到,就在老节点数组的首位新增一个dom节点
image.png
当老开始节点游标>老结束节点游标,代表循环结束了,老的节点数组结束了,新的节点数组还有剩余,剩下的都是新增,追加到老节点数组的队尾。
image.png
当新开始节点游标>新结束节点游标,说明新节点遍历完了,需要删除老节点数组的剩余节点
image.png
网友评论