首先咱们先本身尝试实现一下数据监测。所谓数据监测就是当一个值改变时,用到这个值得地方作出相应改变。里面的核心就是 Object.defineProperty
。数组
var obj = {a: 1}; Object.defineProperty(obj, a, { enumerable: true, configurable: true, get: function(){ // 当调用get方法是,就代表用到了该属性。 //这边的问题就卡在 须要收集什么 } })
咱们收集依赖的目的就是当值改变时,依赖的部分也要作出相应的改变。
可是如今的问题是依赖的地方会有不少,类型也不同。多是template里面用到,多是computed里面计算用到。也有可能用户本身watch监听。先抛开用到地方应该怎么变,想一想,一个动做触发一个事件,不就是回调函数的逻辑吗?
拿最简单的watch来讲:app
var callback = function (newVal, oldVal) { // do something } vm.$watch(obj.a, callback)
这么一看就简单了,咱们收集的依赖就是"回调函数"。
就像上面说的,一个属性用到的地方可能会有不少,所以须要收集的回调函数也不少,所以咱们用一个数组来保存。
同时呢,这是个通用方法,咱们能够封装一下提出来。
所以上面的代码能够改为函数
var obj = {a: 1}; function defineReactive (data, key, val) { var dep = []; Object.defineProperty(obj, a, { enumerable: true, configurable: true, get: function(){ dep.push(callback); // 先无论callback哪来的 }, set: function(newVal){ if(val === newVal) return val = newVal; dep.forEach(function(callback, index){ callback(); }) } }) }
能够把dep封装成一个对象oop
class Dep { constructor(id){ this.id = id; this.deps = []; } addSub( sub ){ this.subs.push(sub) } removeSub (sub){ reomve(this.subs, sub) } notify(){ this.deps.forEach(function(callback, index){ callback(); }) } } function defineReactive (data, key, val) { var dep = new Dep(); Object.defineProperty(obj, a, { enumerable: true, configurable: true, get: function(){ dep.addSub(callback); // 先无论callback哪来的 }, set: function(newVal){ if(val === newVal) return val = newVal; dep.notify(); } }) }
上面虽然借助callback来帮助理解,但真正实现确定不可能真是callback。任何一个函数在不一样的上下文中执行结果都不相同,光拿到要执行的函数确定不行,还得有执行的上下文。所以咱们可用个类包装一下,observe中不关心怎么执行callback,只须要通知一个监听者本身去作更新操做就好。这个监听者就是watcher.this
class Watcher { constructors(component, getter, cb){ this.cb = cb // 对应的回调函数,callback this.getter = getter; this.component = component; //这就是执行上下文 } //收集依赖 get(){ Dep.target = this; this.getter.call(this.component) Dep.target = null; } update(){ this.cb() } }
既然咱们将callback换成了Watcher实例,注意这边,Dep里面收集的Watcher实例,可不是Wacther构造函数。那么在数据的getter方法中就要想办法拿到。咱们将实例存放在Dep中,一个函数对象上。Dep.target = this
,当依赖收集完就销毁 Dep.target = null
。所以Observe
代码能够改为。.net
function defineReactive (data, key, val) { var dep = new Dep(); Object.defineProperty(obj, a, { // .... get: function(){ if(Dep.target){ dep.addSub(Dep.target); // Dep.target是Watcher的实例 } }, // ... }) } class Dep { //... notify(){ this.deps.forEach(function(watcher, index){ watcher.update(); }) } }
大概流程通了,咱们再作点完善。Observe,Dep, Watcher三个关系弄清楚了。如今的问题是,怎么收集依赖和回调。举例来讲:prototype
<div id="app"> <input type="text" v-model="name"/> <div>{{name}}</div> </div> new Vue({ el: "#app", data: { name: "默认值", age: 29 } })
name
属性直接在template中用到。那么只要触发render,就能够收集到依赖。固然,收集到依赖后,须要及时更新。把DOM中的{{name}}
替换成Data
中对应的值。code
这部分代码在 lifecycle.js
的mountComponent
方法中,能够精简为component
export function mountComponent(){ ... callHook(vm, 'beforeMount') //生命周期函数 var updateComponent = () => { // 先经过render收集依赖,再经过update将虚拟DOM中的值同步到真实节点中 vm._update(vm._render(), hydrating) } new Watcher(vm, updateComponent, emptyFunc, { before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') //生命周期函数 } } } vm._isMounted = true callHook(vm, 'mounted') //生命周期函数 }
每一个模板实例化一个Watcher实例。这也与官网的流程图一致server
<div id="app"> <input type="text" v-model="name"/> <div>{{name}}</div> </div> new Vue({ el: "#app", data: { name: "默认值", age: 29 }, watch: { age: function(newValue, oldValue){ console.log("新的值为:" + newValue) } } })
传进来的options会在initState中处理
export function initState (vm: Component) { //vm vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } } ..... function initWatch (vm: Component, watch: Object) { for (const key in watch) { const handler = watch[key] if (Array.isArray(handler)) { for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]) } } else { createWatcher(vm, key, handler) } } } function createWatcher ( vm: Component, expOrFn: string | Function, handler: any, options?: Object ) { if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } return vm.$watch(expOrFn, handler, options) }
能够看到对于options
中的watch
其实就是执行$watch
方法。
Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { const vm: Component = this options = options || {} options.user = true // expOrFn: key, cb: callback watch中的每一个key都实例出Wacther实例 const watcher = new Watcher(vm, expOrFn, cb, options) // immediate: 若是为true就马上执行一次,不然第一次进来不执行,当data改变才会触发执行 if (options.immediate) { cb.call(vm, watcher.value) } return function unwatchFn () { watcher.teardown() } }
同watch,计算属性computed也是在initData中处理。
function initComputed (vm: Component, computed: Object) { // $flow-disable-line const watchers = vm._computedWatchers = Object.create(null) for (const key in computed) { const userDef = computed[key] const getter = typeof userDef === 'function' ? userDef : userDef.get // 能够看到,VUE为每一个computed属性也都生成了一个watcher实例。 //而这边的getter就是计算属性的计算函数。必须先计算一次才能触发依赖的属性的get方法,收集依赖 watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions) if (!(key in vm)) { defineComputed(vm, key, userDef) } } }
这边就再也不放出VUE的数据监听部分源码,能够本身阅读watcher.js
,dep.js
,observer/index.js
。整体代码和咱们本身实现的很像,只是比咱们代码更缜密,多了些其余功能。好比$watcher以后会返回一个取消函数,能够取消监听。
就像上面分析的,一个监听流程的完成必须包含:
咱们知道VUE为template,watch,和computed中的属性实例化了Watcher。而只有在data中的属性才会再initState时进行监听操做。所以咱们能够得出结论,