Vue源码阅读--数组方法的响应式包装和实现
数组原生的方法产生的数据更新原本来说,是不会在使用Vue时发生响应式变化的,而我i们使用push,pop等方法实际上是因为Vue对这些方法做了一层封装,其基本的实现路径如下:
首先,包装监听的过程位于源代码文件夹中的\src\core\observer\array.js部分:
1 import { def } from '../util/index' 2 3 const arrayProto = Array.prototype 4 export const arrayMethods = Object.create(arrayProto) 5 6 const methodsToPatch = [ 7 'push', 8 'pop', 9 'shift', 10 'unshift', 11 'splice', 12 'sort', 13 'reverse' 14 ] 15 16 /** 17 * Intercept mutating methods and emit events 18 */ 19 methodsToPatch.forEach(function (method) { 20 // cache original method 21 const original = arrayProto[method] 22 def(arrayMethods, method, function mutator (...args) { 23 const result = original.apply(this, args) 24 const ob = this.__ob__ 25 let inserted 26 switch (method) { 27 case 'push': 28 case 'unshift': 29 inserted = args 30 break 31 case 'splice': 32 inserted = args.slice(2) 33 break 34 } 35 if (inserted) ob.observeArray(inserted) 36 // notify change 37 ob.dep.notify() 38 return result 39 }) 40 })
实际上,就是通过观察者模式,对数据更新的过程做了一次通知,即37行ob.dep.notify()。这里其实复用了Vue中使用观察者模式实现双向绑定中的代码。双向绑定的流程大概介绍如下图:
这里即通过notify通知各个watch进行updata。
notify的过程可以参看同文件夹下的dep.js,我们可以定位到如下代码:
1 notify () { 2 // stabilize the subscriber list first 3 const subs = this.subs.slice() 4 if (process.env.NODE_ENV !== 'production' && !config.async) { 5 // subs aren't sorted in scheduler if not running async 6 // we need to sort them now to make sure they fire in correct 7 // order 8 subs.sort((a, b) => a.id - b.id) 9 } 10 for (let i = 0, l = subs.length; i < l; i++) { 11 subs[i].update() 12 } 13 } 14 }
这里其实就是对各个观察者进行通知,调用update():
1 update () { 2 /* istanbul ignore else */ 3 if (this.lazy) { 4 this.dirty = true 5 } else if (this.sync) { 6 this.run() 7 } else { 8 queueWatcher(this) 9 } 10 }
抛开一些其他判断和细节,这里下一步我们的主要过程实际上就是进入run():
1 run () { 2 if (this.active) { 3 const value = this.get() 4 if ( 5 value !== this.value || 6 // Deep watchers and watchers on Object/Arrays should fire even 7 // when the value is the same, because the value may 8 // have mutated. 9 isObject(value) || 10 this.deep 11 ) { 12 // set new value 13 const oldValue = this.value 14 this.value = value 15 if (this.user) { 16 const info = `callback for watcher "${this.expression}"` 17 invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info) 18 } else { 19 this.cb.call(this.vm, value, oldValue) 20 } 21 } 22 } 23 }
这里第三行是关键处,在执行run()方法中的get()时。继而会调用当前watcher中的getter()方法,这里就是之前初始化时传入的updateComponent(),执行render函数重绘视图。
继续进入get():
1 get () { 2 pushTarget(this) 3 let value 4 const vm = this.vm 5 try { 6 value = this.getter.call(vm, vm) 7 } catch (e) { 8 if (this.user) { 9 handleError(e, vm, `getter for watcher "${this.expression}"`) 10 } else { 11 throw e 12 } 13 } finally { 14 // "touch" every property so they are all tracked as 15 // dependencies for deep watching 16 if (this.deep) { 17 traverse(value) 18 } 19 popTarget() 20 this.cleanupDeps() 21 } 22 return value 23 }
可以看到关键就是getter(),这里和双向绑定的响应式一样,Vue通过getter的劫持,从而更新界面。
第六行的value = this.getter.call(vm, vm)
this.getter 对应就是 updateComponent 函数,这实际上就是在执⾏:
vm._update(vm._render(), hydrating)
从而实现组件更新。