Vue双向绑定原理
大部分都知道Vue是采用的是对象的get 和set方法来实现数据的双向绑定的过程,本章将讨论他是怎么利用他实现的。
vue双向绑定其实是采用的观察者模式,get和set方法只是实现观察者模式的切入点,即在我们set的时候向观察者发布消息,执行观察者的操作,get的时候是为实现set能够通知watcher进行相关处理做准备。下面我们来看一下数据初始化的流程;
数据初始化流程:
数据在初始化时,会调用方法 defineReactive 为数据绑定dep对象(以备之后使用),在进行挂载时会实例化一个进行页面更新的watcher($watcher).,该watcher调用渲染函数(上图的第5步),会调用模板里涉及到的属性的get方法,即为每个属性的dep对象加载对应的依赖$watcher,同时在$watcher下备份涉及到的dep对象。数据初始化时主要是调用data下的属性的get方法,在数据更新时才会调用属性的set方法,详细可看下面的代码注释。
/** * 采用观察者模式进行数据更新的监听,subject为data下的所有属性以及子孙属性等 * * subject与watcher是多对多的关系 * 一个subject,如属性data.A,可能对于处理页面更新的Watcher,也可能对应$watch函数传入的表达式更新的Watcher对象,或者观察计算属性的Watcher对象等 * 一个更新页面的Watcher会对象data下的所有属性以及子孙属性等 * * * */ function defineReactive( obj, key, val, customSetter, shallow ) { var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key); if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get; if (!getter && arguments.length === 2) { val = obj[key]; } var setter = property && property.set; // 如果 val为对象,创建一个观察者Observe实例ob,先创建一个dep实例,赋值给Observe.dep ,保存实例只val.__ob__属性, 类似val.__ob__ = ob; var childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { var value = getter ? getter.call(obj) : val; //如果属性之前就定义getter方法,则执行getter方法,并返回属性值 if (Dep.target) { // Dep.target为执行更新的watcher对象 dep.depend(); //为该属性添加观察者(订阅者),挂在对应的dep对象的subs:[]属性下,对应的watcher也会存对应的目标关系与watcher.deps与watcher.depIds if (childOb) { //如果是对象,为对象添加该Watcher实例,可用于set, 数组改变,计算属性等操作 childOb.dep.depend(); if (Array.isArray(value)) { //如果value是数据 采用递归的方式为为value下的对象成员添加Observe实例属性 __ob__ dependArray(value); } } } return value }, set: function reactiveSetter(newVal) { var 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 ("development" !== 'production' && customSetter) {//如果是开发环境,者打印一些警告 customSetter(); } if (setter) { //如果属性之前就定义了setter方法,则执行setter方法 setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify(); // 数据有改变,通知观察者进行操作 } }); }
数据更新流程:
这里将讲解数据的更新流程,如果我重设了数据如data.A = 5;它将出发A属性的set方法,该方法会对比当前设置的值与之前的值是否相等,如果不相等,在出发dep.notify()函数,类似触发更新事件,代用更新操作,该方法会去调用dep对象下所有依赖watcher的update方法,该方法会排除重复的watcher,最终采用微任务或者定时的方式去执行watcher对应的run方法(run方法调用了watcher的get方法),之后将会调用渲染函数,如此又将与初始化的过程过程的第五步一致,调用data下的get方法,同时备份watcher至涉及到的dep对象下,将上次执行备份中涉及到的dep而本次执行没涉及到dep下的依赖当前watcher删除,想见 Watcher.prototype.cleanupDeps函数注释
在初始化以及数据更新工程,都将调用watcher.get方法,为什么执行该方法就能保证以上功能正常执行呢
watcher.get方法会去调用watcher实例的getter,如果是进行页面更新与渲染的watcher,getter方法这是去执行render函数并将render函数生成的vnode进行渲染。再执行render函数时会涉及到调用模板里的data属性。从而触发属性的get方法。那又是采用什么方式保证属性对应的dep是触发对应的watcher?见Watcher.prototype.get注释
Watcher.prototype.get = function get() { pushTarget(this); //该函数的作用是,将Dep.target的值推入堆栈中,并将当前的Watcher实例赋值给Dep.target var value; var vm = this.vm; try { value = this.getter.call(vm, vm); } catch (e) { 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 watching
//如果是深度监听watching,进行递归加载监听观察者
if (this.deep) { traverse(value); } popTarget(); //将堆栈中栈顶的值弹出堆栈并赋值给 Dep.target this.cleanupDeps();//与上一次的watcher对象依赖下的dep数据对比,清除没有使用的dep对象 } return value };
在以上代码,在初始化的时候执行watcher.get,其中会调用pushTarget(this),将当前的值赋给Dep.target ,即相当于赋给了一个全局变量。之后将执行watcher的this.getter方法,即render函数,在执行这个函数期间Dep.target一直保持为该watcher,以此保证属性下的dep对象都是对应的watcher,如果在执行render期间有其他watcher执行打断当前的执行,也会在执行其他watcher之后恢复该值,执行完this.getter执行popTarget();将从堆栈中取出执行上下文的watcher值并赋给Dep.target。
下面的代码这是用于保证watcher对象与dep对象的正确依赖关系。同事备份执行watcher依赖的dep对象。
由于dep对象用于通知watcher执行相关的操作,所以他们之间会有一个多对多的对应关系。即dep需要通知哪些watcher,watcher又注册到哪些dep对象中,下面的就是保证每次执行之后这个依赖关系都是正确的
/** * 备份本次更新的deps对象和depIds,同时将上次Watcher更新依赖的dep实例,但本次Watcher更新更新不依赖的dep实例下依赖Watcher备份数据删除该Watcher实例 */ Watcher.prototype.cleanupDeps = function cleanupDeps() { var this$1 = this; var i = this.deps.length; while (i--) { // 获取上一次触发Wactcher更新的dep对象--dep var dep = this$1.deps[i]; // 如果该对象dep在新的watcher依赖下没有则清除dep对象下依赖的该Watcher对象 if (!this$1.newDepIds.has(dep.id)) { dep.removeSub(this$1); } } var tmp = this.depIds; //将上次更新的depIds缓存 this.depIds = this.newDepIds; //备份本次更新的Watcher对应的deps对象的id this.newDepIds = tmp; //将上次更新的depId赋值给newDepIds,并在下一行进行清空 this.newDepIds.clear(); //将newDepIds清空,以备下次更新使用 tmp = this.deps; //deps 对象的备份与 depIds逻辑一致 this.deps = this.newDeps; //备份本次更新的Watcher对应的deps对象 this.newDeps = tmp; this.newDeps.length = 0; //将newDeps清空,以备下次更新使用 };