vue响应式系统之observe、watcher、dep的源码解析

Vue的响应式系统

Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的JavaScript 对象,而当你修改它们时,视图会进行更新,这使得状态管理非常简单直接,我们可以只关注数据本身,而不用手动处理数据到视图的渲染,避免了繁琐的 DOM 操作,提高了开发效率。

vue 的响应式系统依赖于三个重要的类:Dep 类、Watcher 类、Observer 类,然后使用发布订阅模式的思想将他们揉合在一起(不了解发布订阅模式的可以看我之前的文章发布订阅模式与观察者模式)。

vue响应式系统之observe、watcher、dep的源码解析

Observer

Observe扮演的角色是发布者,他的主要作用是调用defineReactive函数,在defineReactive函数中使用Object.defineProperty 方法对对象的每一个子属性进行数据劫持/监听。

部分代码展示

defineReactive函数,Observe的核心,劫持数据,在setter中向Dep(调度中心)添加观察者,在getter中通知观察者更新。

function defineReactive(obj, key, val, customSetter, shallow){ //监听属性key //关键点:在闭包中声明一个Dep实例,用于保存watcher实例 var dep = new Dep(); var getter = property && property.get; var setter = property && property.set; if(!getter && arguments.length === 2) { val = obj[key]; } //执行observe,监听属性key所代表的值val的子属性 var childOb = observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { //获取值 var value = getter ? getter.call(obj) : val; //依赖收集:如果当前有活动的Dep.target(观察者--watcher实例) if(Dep.target) { //将dep放进当前观察者的deps中,同时,将该观察者放入dep中,等待变更通知 dep.depend(); if(childOb) { //为子属性进行依赖收集 //其实就是将同一个watcher观察者实例放进了两个dep中 //一个是正在本身闭包中的dep,另一个是子属性的dep childOb.dep.depend(); } } return value }, set: function reactiveSetter(newVal) { //获取value var value = getter ? getter.call(obj) : val; if(newVal === value || (newVal !== newVal && value !== value)) { return } if(setter) { setter.call(obj, newVal); } else { val = newVal; } //新的值需要重新进行observe,保证数据响应式 childOb = observe(newVal); //关键点:遍历dep.subs,通知所有的观察者 dep.notify(); } }); }

Dep

Dep 扮演的角色是调度中心/订阅器,主要的作用就是收集观察者Watcher和通知观察者目标更新。每个属性拥有自己的消息订阅器dep,用于存放所有订阅了该属性的观察者对象,当数据发生改变时,会遍历观察者列表(dep.subs),通知所有的watch,让订阅者执行自己的update逻辑。

部分代码展示

Dep的设计比较简单,就是收集依赖,通知观察者

//Dep构造函数 var Dep = function Dep() { this.id = uid++; this.subs = []; }; //向dep的观察者列表subs添加观察者 Dep.prototype.addSub = function addSub(sub) { this.subs.push(sub); }; //从dep的观察者列表subs移除观察者 Dep.prototype.removeSub = function removeSub(sub) { remove(this.subs, sub); }; Dep.prototype.depend = function depend() { //依赖收集:如果当前有观察者,将该dep放进当前观察者的deps中 //同时,将当前观察者放入观察者列表subs中 if(Dep.target) { Dep.target.addDep(this); } }; Dep.prototype.notify = function notify() { // 循环处理,运行每个观察者的update接口 var subs = this.subs.slice(); for(var i = 0, l = subs.length; i < l; i++) { subs[i].update(); } }; //Dep.target是观察者,这是全局唯一的,因为在任何时候只有一个观察者被处理。 Dep.target = null; //待处理的观察者队列 var targetStack = []; function pushTarget(_target) { //如果当前有正在处理的观察者,将他压入待处理队列 if(Dep.target) { targetStack.push(Dep.target); } //将Dep.target指向需要处理的观察者 Dep.target = _target; } function popTarget() { //将Dep.target指向栈顶的观察者,并将他移除队列 Dep.target = targetStack.pop(); }

Watcher

Watcher扮演的角色是订阅者/观察者,他的主要作用是为观察属性提供回调函数以及收集依赖(如计算属性computed,vue会把该属性所依赖数据的dep添加到自身的deps中),当被观察的值发生变化时,会接收到来自dep的通知,从而触发回调函数。,

部分代码展示

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/ae605a9102256cddd3be4c17d4947e02.html