对于vue的数据响应模式,不外乎采用设计模式中的观察者模式.即为一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。
那么在vue中,我们都知道 data,props发生变化会让页面重新render等.那么他们必然是观察者模式中的观察对象,即被观察者.那么我们来看一下一个普通的data,vue中对它做了什么处理. 在observer/index.js,核心应该是函数 defineReactive() .在这个函数里面,对object的set和get方法进行了重写.
Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { // 为dept增加watch dep.depend() if (childOb) { childOb.dep.depend() } if (Array.isArray(value)) { dependArray(value) } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() }
那么这个时候又引入了新的东东,Dep,如果此时和Dep一起说,很容易云里雾里,所以我们暂时先不理会Dep,只要知道在set和get的时候调用Dep搞了一些事情就好了.其实这个构造观察目标的过程很简单,无非就是遍历来重写他的get,set方法,以及遇到对象时深度遍历而已.
那么接下来我们就来看观察者watcher.我们在使用过程中,我们在使用过程中.最明显的创建 watch便是创建了观察者.compute,包括render方法,都是创建了watcher,所以,创建watcher过程中.
expOrFn: string | Function,
它可能是一个string或者一个function.在watcher中,
addDep (dep: Dep)
cleanupDeps( )
等方法均是对依赖(别观察者)的增删.update则是响应被观察者的变化.无非是 函数 的话执行一遍.值的话取值,有回调的执行回调.也是很简单.
那么现在还缺少一个轴,将观察者和被观察者链接到一起的轴.这时dep这个类出现了.每一个观察对象都拥有一个dep的实例.而这个实例中,又存储了所有的观察者.这样就形成了观察者模式中的一对多关系.而 notify方法实际上就是通知它所有的watcher进行update操作.(为什么要这么个东东,而不直接wacher和observer关联,其实我也不知道啦啦啦~~~).
上面介绍的东西实质上都平淡无奇,很好理解.那么最后一个问题来了,observer如何与其对应的watcher关联起来.那么这时候就体现出作者的构思巧妙了. 实际上wacher 在create 的过程中,如果是function,会执行一遍,如果是exp,将它构造成一个简单函数:
// 从vm中取到值 return function (obj) { for (let i = 0; i < segments.length; i++) { if (!obj) return obj = obj[segments[i]] } return obj }
在执行之前,调用
pushTarget(this)
也就是将自身(watcher)暂存到Dep.target上,执行过程中,如果observer的get方法,那么会执行get方法中
dep.depend()
observer会把watcher加入到自己的dep队列中,成为自身观察者之一.十分简单,但是确实有效.
基本分析完毕.