最近一段时间在阅读Vue源码,从它的核心原理入手,开始了源码的学习,而其核心原理就是其数据的响应式。而且结合设计模式进行学习html
这里简短的介绍这两种模式的联系和差别,react
观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,全部依赖于它的对象都将获得通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通信,观察者模式就是观察者和被观察者之间的通信。git
观察者模式有一个别名叫“发布-订阅模式”,或者说是“订阅-发布模式”,订阅者和订阅目标是联系在一块儿的,当订阅目标发生改变时,逐个通知订阅者。咱们能够用报纸期刊的订阅来形象的说明,当你订阅了一份报纸,天天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,报社和订报纸的客户就是上面文章开头所说的“一对多”的依赖关系。github
其实24种基本的设计模式中并无发布订阅模式,上面也说了,他只是观察者模式的一个别称。设计模式
可是通过时间的沉淀,彷佛他已经强大了起来,已经独立于观察者模式,成为另一种不一样的设计模式。数组
在如今的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为调度中心或事件通道,它维持着发布者和订阅者之间的联系,过滤全部发布者传入的消息并相应地分发它们给订阅者。闭包
举一个例子,你在微博上关注了A,同时其余不少人也关注了A,那么当A发布动态的时候,微博就会为大家推送这条动态。A就是发布者,你是订阅者,微博就是调度中心,你和A是没有直接的消息往来的,全是经过微博来协调的(你的关注,A的发布动态)。异步
能够看出,发布订阅模式相比观察者模式多了个事件通道,事件通道做为调度中心,管理事件的订阅和发布工做,完全隔绝了订阅者和发布者的依赖关系。即订阅者在订阅事件的时候,只关注事件自己,而不关心谁会发布这个事件;发布者在发布事件的时候,只关注事件自己,而不关心谁订阅了这个事件。函数
观察者模式有两个重要的角色,即目标和观察者。在目标和观察者之间是没有事件通道的。一方面,观察者要想订阅目标事件,因为没有事件通道,所以必须将本身添加到目标(Subject) 中进行管理;另外一方面,目标在触发事件的时候,也没法将通知操做(notify) 委托给事件通道,所以只能亲自去通知全部的观察者。oop
当咱们在data
中定义一个值的时候,以下:
const vm = new Vue({ data() { return { message: '' } }, template: '<div>{{message}}</div>' }) vm.message = 'hello';
此时Vue
内部发生了什么,下面列出须要解决的问题以下:
data
中的值发生改变时,是如何更新视图的
上面是表示定义一个data
值的时候,内部这个流程是如何的,结合讲解相信你对响应式原理有更深刻的理解。为了让结构更加清晰,这里只考虑一个视图,而且不会有computed
的状况。
在讲解原理以前,首先对几个单词进行定义:
Observer
首先看看当实例化Vue
的时候,对data
是如何进行处理的
_init => mount => this._watcher = new Watcher(vm, updateComponent, noop) => Dep.target = this._watcher => observe(data, true) => new Observer(data)
new Vue
会调用_init
函数mount
把须要渲染的模板挂载到元素上Watcher
实例Watcher
实例赋值给Dep.target
data
返回的数据进行observe
new Observer
遍历data
进行setter
和getter
绑定下面来看看observe
函数的实现:
function observe(value, asRootData) { let ob; // 检测当前数据是否被observe过,若是是则没必要重复绑定 if(hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else { ob = new Observer(value); } if (asRootData && ob) { ob.vmCount++; } return ob; }
首先调用的就是上面这个函数,__ob__
用户判断是否有Observer
实例,若是有就使用原来的,若是没有就建立一个新的Observer
实例。vmCount
表示该Vue
实例使用的次数,asRootData
表示是不是data
的跟,例如在一个template
中一个相同的组件使用了两次:
<div> <my-component /> <my-component /> </div>
这个时候vmCount
就为2
。接下来看Observer
的实现:
class Observer { constructor(value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; def(value, '__ob__', this) if (Array.isArray(value)) { // 若是是数组则须要遍历数组的每一个成员进行observe // 这里会对数组原有的方法进行从新定义 this.observeArray(value) } else { // 若是对象则调用下面的程序 this.walk(value) } } walk(obj) { const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } }
下图是Observer
类的结构
这里主要就是遍历data
中定义的值,而后在每一个遍历的属性下面添加__ob__
,而后在__ob__
定义Dep
,根据数据类型的不一样调用不一样的方法,若是是数组则使用observeArray
,该方法会重写数数组的7种方法,对数组的每一个成员调用observe
函数,若是是普通对象,则遍历他的属性调用defineReactive
,进行getter/setter
绑定。 defineReactive
是Vue
最核心的内容,使用方法如: defineReactive(obj, keys[i], obj[keys[i]])
。当在data
中定义一个属性的时候,当咱们更改该值的时候,视图是如何知道,这个值发生了改变来更新视图的。
function defineReactive(obj, key, val) { // 在闭包中定义一个dep对象 const dep = new Dep(); // 对象的子对象递归进行observe并返回子节点的Observer对象 let childOb = observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { const value = getter ? getter.call(obj) : val; if (Dep.target) { // 进行依赖收集,dep.depend就是将dep和watcher进行互相绑定 // Dep.target表示须要绑定的watcher dep.depend(); if (childOb) { // 子对象进行依赖收集,其实就是将同一个watcher观察者实例放进两个depend中 // 一个是正在自己闭包中的depend,另外一个是子元素的depend childOb.dep.depend(); } if (Array.isArray(value)) { // 若是是数组,须要对数组的每一个成员都进行依赖收集 dependArray(value) } } return value; }, set: function reactiveSetter(newVal) { // 经过getter方法获取当前值,与新值发生比较,一致则不须要执行下面的操做 const value = getter ? getter.call(obj) : val; if (newVal === value || (newVal !== newVal && value !== value)) { return false; } if (setter) { setter.call(obj, newVal) } else { val = newVal } // 新的值须要从新observe,保证数据响应式 childOb = observe(newVal) // 通知全部观察者 dep.notify() } }) }
经过Object.defineProperty
把数据进行了getter
和setter
绑定。getter
用于依赖收集,setter
用于经过dep
去通知watcher
, watcher
进行执行变化。
如何进行依赖收集的,能够经过一个例子进行解释:
data() { return { message: [1, 2] } }
结合一个流程图进行分析上面例子:
observe(data) => data.__ob__ = new Observer(data) => walk(data) => childOb = observe(message) => message.__ob__ = new Observer(data) => message.__ob__.dep = new Dep; => childOb ? childOb.dep.depend();
分析其过程就是:
data
函数返回的对象添加__ob__
,返回具体的内容以下:const res = { message: [1, 2] __ob___: new Observer(data) }
res
, 由于res
为对象,因此执行walk
observe(message)
message
添加__ob__
,__ob__
上存在一个dep
用于依赖收集childOb = message.__ob__
,此时同一个watcher
放入子对象中,也就是message.__ob__.dep
中回顾上面的分析,就可以区分出Observer
和defineReactive
中两个dep
的区别了,这两个地方都声明了new Dep
,Observer
的dep
用于收集对象和数组的订阅者,挂载在对象的属性上。当对象或者数组增删元素时调用$set
,获取到__ob__
进行依赖收集,而后调用ob.dep.notify
j进行更新。在defineReactive
中,这个dep
是存在一个闭包中,这是对对象属性服务的,在获取属性值的时候进行依赖收集,设置属性值的时候发布更新。
Dep
下面来介绍一下dep
,源码以下:
let uid = 0; class Dep { constructor() { this.id = uid++; this.subs = [] } // 添加一个订阅者 addSub(sub) { this.subs.push(sub) } // 移除一个观察者对象 removeSub(sub) { remove(this.subs, sub) } // 依赖收集,当存在Dep.target的时候添加观察者对象 depend() { if (Dep.target) { Dep.target.addDep(this) } } // 通知全部订阅者 notify() { const subs = this.subs.slice(); for(let i = 0, l = subs.length; i < l; i++) { subs[i].update(); } } }
结构以下:
当对象中的属性触发get
的时候,先前defineReactive
中const dep = new Dep()
闭包中,就会把当前的Watcher
订阅者加入到subs
中。Dep
是发布订阅者模型中的发布者,Watcher
是订阅者,一个Dep
实例对应一个对象属性或一个被观察的对象,用于收集和数据改变时,发布更新。好比说有这个一个data
data() { return { message: 'a' } }
触发视图有两种方法:
getter/setter
,从新设置message
的值,设置的过程当中会触发dep.notify
进行发布更新, 好比this.message = 'b'
$set
函数: this.$set(this.message, 'fpx', 'number-one')
,这会获取到message
的__ob__
上的dep
进行发布更新Watcher
Watcher
是一个订阅者。依赖收集后watcher
会被存放在Dep
的subs
中,数据变更的时候经过dep
发布者发布信息,相关的订阅者watcher
收到信息后经过cb
进行视图更新。Watcher
内容不少,咱们只关注最重要的一些部分:
class Watcher { constructor(vm, expOrFn, cb, options) { this.vm = vm; // 存放订阅者实例 vm._watchers.push(this) this.deps = []; this.newDeps = [] this.depsIds= new Set(); this.newDepIds new Set(); if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) } this.value = this.get(); } get() { pushTarget(this) const vm = this.vm; value = this.getter.call(vm, vm); popTarget(); this.cleanupDeps(); return value } // 添加一个依赖关系到Deps集合中 addDep(dep) { const id = dep.id; if (!this.newDepsIds.has(id)) { this.newDepsIds.add(id) this.newDeps.push(dep); // 这里作一个去重,若是depIds里包含这个id,那么以前给depId添加这个id的时候 // 已经调用过dep.addSub(this),避免了重复添加 if (!this.depIds.has(id)) { dep.addSub(this) } } } // 用于更新模板 update() { if (this.sync) { // 同步则执行run直接渲染视图 this.run(); } else { // 异步推送到观察者队列中,下一个tick时调用,最后会调用run方法 queueWatcher(this) } } // 收集该watcher的全部deps原理 depend() { let i = this.deps.length; while(i--) { this.deps[i].depend(); } } }
Watcher
结构以下:
首先仍是理清Watcher
构造函数作的事情:
Dep.target = new Watcher(vm, updateComponent, noop = {}) => 初始化变量 => 获取getter函数 => 调用get函数,get函数会调用getter函数,从而收集依赖
在建立Vue
实例的时候,触发getter
就会进行依赖收集,下面是这几种状况:Watcher
有四个使用的场景,只有在这四种场景中,Watcher
才会收集依赖,更新模板或表达式
Vue
实例时watch
选项里的数据computed
选型里的数据所依赖的数据$watch
观察的数据或者表达式在前面代码中声明了Dep.target
,这个是干吗用的呢。在前面提到依赖收集的时机,是当咱们获取元素属性值的时候,可是此时不知道哪一个是正确的watcher
,因此定义一个全局变量记录当前的Watcher
,方便添加当前正在执行的Watcher
。Watcher
对象中有两个属性: deps
和newDeps
。他们用来记录上一次Watcher
收集的依赖和新一轮Watcher
收集的依赖,每一次数据的更新都须要从新收集依赖, 流程以下:
setter => notify => run => get
当数据发布更新后,会调用notify
方法,notify
会调用run
方法,run
方法会调用get
方法,从新获取值,从新进行依赖收集。举一个上面的例子,若是咱们更改了message
的值,而且模板依赖了新更改的值,this.message = {key: 'val'}
,由于上一轮没有对新值进行依赖,因此这一轮须要从新收集依赖。
在Vue
初始化的时候,会生成一个watcher
,依赖收集就是经过属性的getter
完成的。结合文章开头给出的图片,Observer
和Dep
是一对一的关系,Dep
和Watcher
是多对多的关系,Dep
则是Observer
和Watcher
之间的纽带。依赖收集完成偶,当属性变化会执行被Observer
对象的dep.notify()
方法,这个方法会遍历订阅者Watcher
列表向其发送消息,Watcher
会执行run
方法去更新视图。
原本还想讲点computed
的,可是估计您看着也累,我写着也累,computed
将由另一篇文章进行讲解。
一篇文章写下来,很有些难度。下面有三点:
因此给出一些措施来弥补这些问题:
第一次写这种源码分析文章,诸多不足,欢迎你们提出宝贵的建议,也请多多关注个人GitHub~~