vue2.x核心源码深入浅出,我还是去看源码了( 三 )


然后setter里主要是调用了dep.notify(),notify()源码定义在https://github.com/vuejs/vue/blob/v2.7.10/src/core/observer/dep.ts#L51,其作用是遍历subs数组,然后通知到与当前属性相关的每个watcher实例,调用watcher.update()触发视图更新,这个过程叫做派发更新 。
export default class Dep {static target?: DepTarget | nullid: numbersubs: Array<DepTarget>constructor() {this.id = uid++this.subs = []}addSub(sub: DepTarget) {this.subs.push(sub)}removeSub(sub: DepTarget) {remove(this.subs, sub)}depend(info?: DebuggerEventExtraInfo) {if (Dep.target) {Dep.target.addDep(this)if (__DEV__ && info && Dep.target.onTrack) {Dep.target.onTrack({effect: Dep.target,...info})}}}notify(info?: DebuggerEventExtraInfo) {// stabilize the subscriber list firstconst subs = this.subs.slice()if (__DEV__ && !config.async) {// subs aren't sorted in scheduler if not running async// we need to sort them now to make sure they fire in correct// ordersubs.sort((a, b) => a.id - b.id)}for (let i = 0, l = subs.length; i < l; i++) {if (__DEV__ && info) {const sub = subs[i]sub.onTrigger &&sub.onTrigger({effect: subs[i],...info})}subs[i].update()}}}然后,我们再看看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() 。如果节点是文本节点且文本不一样的,直接将旧节点的文本设置为新节点的文本 。

经验总结扩展阅读