目标
掌握虚拟DOM和Diff算法
知识点
- 虚拟dom,可以借鉴snabbdom。
- patch对比。
Vue 虚拟dom
-
虚拟dom(Virtual DOM)是对DOM的JS抽象表示,它们是JS对象。能够描述DOM结构和关系。应用各种状态变化会作用于虚拟DOM,最终映射到DOM上。
-
Vue中虚拟dom基于snabbdom实现的。
-
虚拟DOM轻量,快速:当它们发生变化时通过新旧虚拟DOM对比可以得到最小的DOM操作量,配合异步更新策略减少刷新频率,从而提升性能。·
patch(vnode, h('div', obj.foo)) -
跨平台;将虚拟DOM更新转换不同运行时特殊操作实现跨平台。
-
兼容性:可以加入兼容代码增强操作的兼容性。
-
必要性:vue1.x版本有细粒度的数据变化侦测,它是不需要虚拟DOM的。但是细粒度灰造成大量的开销,这对于大型项目来说是不可接受的。因此Vue2.x版本引进了虚拟DOM的概念,因此,Vue2.x选择了中等粒度的解决方案,每一组件一个watch实例,这样的状态变化时只能通知到组件,再通过虚拟DOM去进行对比和渲染。
Vue Diff
src/core/instance/lifecycle.js
mountComponent 渲染,更新组件
// 定义更新函数 updateComponent = () => { // 调用lifeCycleMixin中定义的_update和renderMixin中定义的_render vm._update(vm._render(), hydrating); }
src/core/instance/render.js
_render 生成虚拟DOM
src/core/instance/lifecycle.js
_update 负责更新dom,将vnode转换为dom
src/platforms/web/runtime/index.js
patch是在平台特有代码中指定的
// install platform patch function Vue.prototype.__patch__ = inBrowser ? patch : noop
流程明细
Watch.run() => componentUpdate() => render() => update() => patch()
Diff算法
同层比较,深度优先遍历。
patch原理
src/core/vdom/patch.js
- oldVnode老节点有值, 但是vnode新节点是空,销毁oldVnode老节点 - oldVnode老节点是空,vnode新节点是有值情况,创建根节点 - oldVnode老节点有值,vnode新节点也有值的情况,进行**patchVnode**打补丁 - **patchVnode** 比较两个vnode,包括三种类型操作:**属性更新**,**文本更新**,**子节点更新**。 - oldVnode老节点和vnode新节点一样不用diff - 新老节点都有children,比较children,调用**updateChildren**。 - **updateChildren** 主要的作用是一种高效的方式对比新旧两个VNode的children得出最小的补丁。执行一个双循环的传统的方式,vue种针对web场景特点做了特别的算法优化。
- 创建4个游标,4个游标不能相交,相交停止循环。 - oldStartVnode是空的情况,++oldStartIdx,右移一位 - oldEndVnode是空的,--oldEndIdx,左移一位 - oldStartVnode和newStartVnode对比完成,++oldStartIdx, ++newStartIdx, 同时右移一位 - oldEndVnode和newEndVnode对比完成,--oldEndIdx, --newEndIdx, 同时左移一位 - oldStartVnode和newEndVnode对比完成执行插入操作,++oldStartIdx, 右移一位,--newEndIdx,左移一位 - oldEndVnode和newStartVnode对比完成执行插入操作,--oldEndIdx, 左移一位,++newStartIdx, 右移一位 - 上面几种情况都没有找到的话,就老老实实查找, 循环节点进行对比。 - 最后oldStartIdx > oldEndIdx 批量添加节点, newStartIdx > newEndIdx 批量删除节点。 - oldCh老节点的children是空的,ch新节点的children不是空的,批量添加节点。 - oldCh老节点的children不是空的,ch新节点的children是空的,批量删除节点。 - oldVnode老节点的内容不是空的,vnode新节点是空的,清空内容。 - oldVnode老节点和vnode新节点都是文本,对比文本不一样进行替换。 - oldVnode老节点有元素,vnode新节点没有元素,清空oldVnode元素
结语
- 在Vue2.x版本中引入虚拟DOM是必然的选择,Vue的响应式关联到多个Watcher更新,一旦Watcher过多性能就会降低,这也是Vue不能支持大型项目的原因。引入虚拟DOM之后一个组件对应一个Watcher这样的话颗粒度就会变小相应的会提升性能,但是如果一个组件对应一个Watcher实例的话,使用单纯的循环操作数据量大的话复杂度就会变高,所以Vue2.x版本的Diff算法巧妙的解决了这种问题。
- Vue Diff算法在新老两组VNode节点的左右头尾两侧做了标记,在遍历过程中这几个变量都会向中间靠拢。当oldStartIdx > oldEndIdx或者newStartIdx > newEndIdx时结束循环。这样对比一圈之后大部分的节点就会对比完成。如果还有没有对比到VNode节点就进行遍历对比判断。最后如果新VNode节点多于老VNode节点就批量添加。如果新VNode节点少于老VNode节点就批量删除。