然后,我们再看看Watcher类 , 源码定义在https://github.com/vuejs/vue/blob/v2.7.10/src/core/observer/watcher.ts 。主要看下列代码,在#L196 。
update() {/* istanbul ignore else */if (this.lazy) {this.dirty = true} else if (this.sync) {this.run()} else {queueWatcher(this)}}前面说到,派发更新会触发相关watcher实例的update(),而update()主要是执行了queueWatcher(),这个queueWatcher()定义在https://github.com/vuejs/vue/blob/v2.7.10/src/core/observer/scheduler.ts#L166 , 代码如下,主要是起到了对watcher实例去重,然后会在flushSchedulerQueue队列中进行排序,并一个个调用了队列中的watcher.run() , 最后用nextTick去异步执行flushSchedulerQueue使视图产生更新 。
export function queueWatcher(watcher: Watcher) {const id = watcher.idif (has[id] != null) {return}if (watcher === Dep.target && watcher.noRecurse) {return}has[id] = trueif (!flushing) {queue.push(watcher)} else {// if already flushing, splice the watcher based on its id// if already past its id, it will be run next immediately.let i = queue.length - 1while (i > index && queue[i].id > watcher.id) {i--}queue.splice(i + 1, 0, watcher)}// queue the flushif (!waiting) {waiting = trueif (__DEV__ && !config.async) {flushSchedulerQueue()return}nextTick(flushSchedulerQueue)}}这里可以看下wacther.run()的代码,源码定义在https://github.com/vuejs/vue/blob/v2.7.10/src/core/observer/watcher.ts#L211,其重点是它调用了Watcher类自身的get(),本质是调用data中的get() ,其作用是开启新一轮的依赖收集 。
pushTarget(this)let valueconst vm = this.vmtry {value = https://www.huyubaike.com/biancheng/this.getter.call(vm, vm)} catch (e: any) {if (this.user) {handleError(e, vm, `getter for watcher"${this.expression}"`)} else {throw e}} finally {// "touch" every property so they are all tracked as// dependencies for deep watchingif (this.deep) {traverse(value)}popTarget()this.cleanupDeps()}return value}3.diff算法
vue更新节点并不是直接暴力一个个节点全部更新 , 而是对新旧节点进行比较,然后进行按需更新:创建新增的节点,删除废除不用的节点,然后对有差异的节点进行修改或移动 。diff算法主要是靠patch()实现的 , 主要调用的是patchVnode()和updateChildren()这两个方法,源码分别定义在https://github.com/vuejs/vue/blob/v2.7.10/src/core/vdom/patch.ts#L584和#L413,前者的作用是先对比了新老节点 , 然后对一些异步占位符节点(#603的oldVnode.isAsyncPlaceholder,这个属性在vnode.ts中没有注释 , vnode源码定义在https://github.com/vuejs/vue/blob/v2.7.10/src/core/vdom/vnode.ts#L8,应该是可以理解为异步组件的占位符)或是静态节点(#617的vnode.isStatic)且含有一样key值的节点且是【克隆节点(#620的vnode.isCloned)或v-once指令绑定的节点(#620的vnode.isOnce,只渲染一次)】不予更新,以提升性能 。不是文本节点(#638的vnode.text)的话,就需要对比新旧子节点,对新旧子节点进行按需更新:新子节点有旧子节点没有则新建addVnodes(),新子节点没有旧子节点有则删除removeVnodes(),其他的更新updateChildren() 。如果节点是文本节点且文本不一样的,直接将旧节点的文本设置为新节点的文本 。
if (isTrue(oldVnode.isAsyncPlaceholder)) {if (isDef(vnode.asyncFactory.resolved)) {hydrate(oldVnode.elm, vnode, insertedVnodeQueue)} else {vnode.isAsyncPlaceholder = true}return}// reuse element for static trees.// note we only do this if the vnode is cloned -// if the new node is not cloned it means the render functions have been// reset by the hot-reload-api and we need to do a proper re-render.if (isTrue(vnode.isStatic) &&isTrue(oldVnode.isStatic) &&vnode.key === oldVnode.key &&(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {vnode.componentInstance = oldVnode.componentInstancereturn}而后者的作用是进行是定义了新旧子节点数组的头和尾 , 然后新旧子节点数组头尾交叉对比,它只在同层级进行比较,不会跨层级比较,这是有考量的,因为前端实际的操作中 , 很少会把dom元素移到其他层级去 。比较完子节点之后,就开始递归调用patchVnode()更新子节点了,这里考点就来了:vue的diff算法是深度优先算法还是广度优先算法?从这个更新流程可以看出来,正常调用顺序是patch()->patchVnode()->updateChildren()->patchVnode()->updateChildren()->....这是深度优先算法,同层比较,深度优先 。
function updateChildren(parentElm,oldCh,newCh,insertedVnodeQueue,removeOnly) {let oldStartIdx = 0let newStartIdx = 0let oldEndIdx = oldCh.length - 1let oldStartVnode = oldCh[0]let oldEndVnode = oldCh[oldEndIdx]let newEndIdx = newCh.length - 1let newStartVnode = newCh[0]let newEndVnode = newCh[newEndIdx]let oldKeyToIdx, idxInOld, vnodeToMove, refElm// removeOnly is a special flag used only by <transition-group>// to ensure removed elements stay in correct relative positions// during leaving transitionsconst canMove = !removeOnlyif (__DEV__) {checkDuplicateKeys(newCh)}while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {if (isUndef(oldStartVnode)) {oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left} else if (isUndef(oldEndVnode)) {oldEndVnode = oldCh[--oldEndIdx]} else if (sameVnode(oldStartVnode, newStartVnode)) {patchVnode(oldStartVnode,newStartVnode,insertedVnodeQueue,newCh,newStartIdx)oldStartVnode = oldCh[++oldStartIdx]newStartVnode = newCh[++newStartIdx]} else if (sameVnode(oldEndVnode, newEndVnode)) {patchVnode(oldEndVnode,newEndVnode,insertedVnodeQueue,newCh,newEndIdx)oldEndVnode = oldCh[--oldEndIdx]newEndVnode = newCh[--newEndIdx]} else if (sameVnode(oldStartVnode, newEndVnode)) {// Vnode moved rightpatchVnode(oldStartVnode,newEndVnode,insertedVnodeQueue,newCh,newEndIdx)canMove &&nodeOps.insertBefore(parentElm,oldStartVnode.elm,nodeOps.nextSibling(oldEndVnode.elm))oldStartVnode = oldCh[++oldStartIdx]newEndVnode = newCh[--newEndIdx]} else if (sameVnode(oldEndVnode, newStartVnode)) {// Vnode moved leftpatchVnode(oldEndVnode,newStartVnode,insertedVnodeQueue,newCh,newStartIdx)canMove &&nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)oldEndVnode = oldCh[--oldEndIdx]newStartVnode = newCh[++newStartIdx]} else {if (isUndef(oldKeyToIdx))oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)idxInOld = isDef(newStartVnode.key)? oldKeyToIdx[newStartVnode.key]: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)if (isUndef(idxInOld)) {// New elementcreateElm(newStartVnode,insertedVnodeQueue,parentElm,oldStartVnode.elm,false,newCh,newStartIdx)} else {vnodeToMove = oldCh[idxInOld]if (sameVnode(vnodeToMove, newStartVnode)) {patchVnode(vnodeToMove,newStartVnode,insertedVnodeQueue,newCh,newStartIdx)oldCh[idxInOld] = undefinedcanMove &&nodeOps.insertBefore(parentElm,vnodeToMove.elm,oldStartVnode.elm)} else {// same key but different element. treat as new elementcreateElm(newStartVnode,insertedVnodeQueue,parentElm,oldStartVnode.elm,false,newCh,newStartIdx)}}newStartVnode = newCh[++newStartIdx]}}if (oldStartIdx > oldEndIdx) {refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elmaddVnodes(parentElm,refElm,newCh,newStartIdx,newEndIdx,insertedVnodeQueue)} else if (newStartIdx > newEndIdx) {removeVnodes(oldCh, oldStartIdx, oldEndIdx)}}
推荐阅读
- Ruoyi字典源码学习
- 【Spring boot】启动过程源码分析
- 八 Netty 学习:新连接接入源码说明
- 深入底层C源码 Redis核心设计原理
- 七 Netty 学习:NioEventLoop 对应线程的创建和启动源码说明
- ERP 系统的核心是什么?有什么作用?
- Spring mvc源码分析系列--Servlet的前世今生
- spring cron表达式源码分析
- 集合框架——LinkedList集合源码分析
- 含源码 手把手教你使用LabVIEW OpenCV DNN实现手写数字识别