这篇文章主要分析一下Vue数据响应,并且通过这篇文章你将了解到:
1.Vue数据响应式的设计思想
2.观察者模式
3.了解Observer,Dep,Watcher的源码实现原理
首先,Vue使用的数据响应使用了数据劫持的方法:
当我们在new Vue()
的时候将data属性下的对象转化成可观察的,用的是这个方法: Observer.defineProterty()
。当数据改变的时候触发set()
,当获取数据时触发get()
。
使用了观察者模式来实现数据响应:
观察者模式
观察者模式也称为发布-订阅模式。主要用于处理不同对象之间的交互通信问题。
发布者:当有事件触发的时候,通知订阅者。
订阅者:当收到发布者的通知时,做出响应的反应。
观察者模式的使用场景:当一个对象的状态发生变化时,所有的依赖对象都将得到通知。
先来看看Vue中new一个实例时
new Vue({
data(){
return {
name: 'Helen',
age: 18
}
},
watch: {
age(newVal){
...
cb()
}
}
})
后面我将围绕这一个示例来讲解,我们先在data中将name
和age
转成可观察的,然后watch age
这个属性,当age
改变时,执行回调函数。先想一下实现这个功能的思路。
首先,我们要将在data
中声明了的属性变成可观察的,然后再去监听这个属性。
class Observer{
constructor(data){
this.walk(data)
}
walk(){
//遍历data的每一个属性
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
}
function defineReactive(data, key, val){
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get(){
return val
},
set(newVal){
val = newVal
}
})
}
上面的代码将对在new Observer
对象时,初始化是执行walk
,data
的属性循环调用defineReactive
。defineReactive
将data
的属性转成访问器属性。当修改data
属性值时,可以通过set
get
获取通知。现在还需要一个Dep来存放依赖
class Dep{
this. depends = []
}
当data中的属性改变时,使用Dep
来触发订阅者,当获取值时,用Dep
来收集依赖。
然后还需要一个Watcher,在依赖发生改变的时候将执行Watcher
。源码中有三处用到了new Wacher
。
1.在更新视图的时候,updateComponent
2.watch 属性的时候:$watcher
(就是实例中用到的)
3.computed
其实讲到这里,已经可以看出Watcher其实是一个订阅者。只有当data的数据改变的时候触发。
怎么将Dep和Watcher向关联呢?
class Watcher{
constructor(exp, cb){
this.exp = exp //exp 是监听的属性
this.cb = cb //cb是回调函数
//多加了这一句
this.value = data[exp]
}
}
this.value = data[exp]
这一句的作用是什么?data
已经变成访问器对象了,获取data
属性的值就是触发了Observer
的get
,额。。这就关联起来了。
现在需要添加代码:在获取属性时收集依赖,在设置属性值时发送通知执行watcher。
function defineReactive(data, key, val){
let dep = new Dep()
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get(){
//收集依赖
dep.depend()
return val
},
set(newVal){
//发送通知
dep.notify()
val = newVal
}
})
}
//在修改一下Dep类
class Dep{
this. depends = []
depend(){
//这里需要将Watcher.target这个静态属性设置成一个Watcher的实例,然后加入到依赖的数组
this.depends.push(Watcher.target)
}
notify(){
//遍历依赖,执行回调函数
this.depends.forEach(depend => {
depend.cb()
})
}
}
//在修改一下watcher
class Watcher{
constructor(exp, cb){
this.exp = exp
this.cb = cb
//将Watcher.target设置成实例
Watcher.target = this
this.value = data[exp]
}
}
下面再看一遍源码:
function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: Function
) {
//每一个属性都有一个dep实例,这个Dep实例在get和set闭包作用域链中
const dep = new Dep()
//返回属性描述符
const property = Object.getOwnPropertyDescriptor(obj, key)
//如果该属性不能被删除或修改则不继续执行
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
/**
* 直接将对象描述符的get和set封装成getter和setter函数
* 当没有手动设置get和set的时候,getter和setter是undefined
*/
const getter = property && property.get
const setter = property && property.set
//进行递归绑定
let childOb = observe(val)
//每一个属性都要变为可观察的
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
//这里只有Dep.target存在的情况下才收集依赖,Dep.target其实是
//一个watcher对象,全局的Dep.target是唯一的,只有在watcher在
//收集依赖的时候才会执行dep.depend(),在直接使用js访问属性的
//时候直接取值
if (Dep.target) {
dep.depend()
//递归绑定
if (childOb) {
childOb.dep.depend()
}
}
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 = observe(newVal)
//data对象的属性值改变就会触发notify来通知订阅者
dep.notify()
}
})
}
通过上面代码的注释,可以知道Dep
是在收集订阅者,每一个data对象的属性都有一个Dep
,每一个Dep
可以有多个订阅者,这一句dep.depend()
其实很关键,其实是在收集订阅者Watcher
。
再来看一下Dep部分的代码:
class Dep {
static target: ?Watcher;
id: number;
subs: Array; //subs存的是这个Dep的所有订阅者Watcher
constructor () {
this.id = uid++ //每个Vue实例中的每一个Dep实例都有不同的id
this.subs = [] //用来收集订阅者
}
//添加订阅者
addSub (sub: Watcher) {
this.subs.push(sub)
}
//删除订阅者
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
//给watcher收集依赖
//这里是一个关键步骤,Dep.target是一个watcher实例
//先将这个Dep实例添加到Watcher的依赖中
//然后在watcher中调用dep.addSub将watcher添加到dep的订阅者中。
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
//通知订阅者,数据有更新
notify () {
// stablize the subscriber list first
const subs = this.subs.slice()
//遍历这个依赖的所有订阅者watcher
for (let i = 0, l = subs.length; i < l; i++) {
//update()的最终目的就是要执行Watcher的getter
//执行这个Watcher的getter的时候就会触发这个Watcher的依赖们的get()
//然后重新收集依赖
subs[i].update()
}
}
}
注释已经写得很详细了,先看看Watcher的代码,再讲一下Dep的问题
class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deps: Array;
newDeps: Array;
depIds: Set;
newDepIds: Set;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: Object
) {
this.vm = vm
vm._watchers.push(this)
this.cb = cb
this.id = ++uid // uid for batching
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
//expOrFn可以是字符串,也可能是函数,如果是函数就直接赋给
//this.getter,如果是字符串表达式就回去parsePath
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
}
//这里在初始化时就会去执行get(),然后在get()中执行expOrFn
//(后面讲详细讲这里)。
this.value = this.lazy
? undefined
: this.get()
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
//Watcher收集依赖
get () {
//pushTarget函数将target设置为该watcher实例
pushTarget(this)
const value = this.getter.call(this.vm, this.vm) //就在这一步收集依赖
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
//清理依赖
this.cleanupDeps()
return value
}
/**
* Add a dependency to this directive.
*/
//添加依赖
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
//去重
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
/**
* Clean up for dependency collection.
*/
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
//如果这个watcher不依赖与某个数据就要把这个依赖给删除
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
//更新depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
//清空newDepIds
this.newDepIds.clear()
tmp = this.deps
//这里赋值了this.deps
//更新deps
this.deps = this.newDeps
this.newDeps = tmp
//清空newDeps
this.newDeps.length = 0
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
//update的最终都会执行this.run()
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
//run方法中,会执行this.get(),就会重新收集依赖
run () {
if (this.active) {
//重点是这里,会this.get()
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
//在$watch的时候用的回调函数
this.cb.call(this.vm, value, oldValue)
}
}
}
/**
* Depend on all deps collected by this watcher.
*/
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
}
我们来想一下什么时候expOrFn
是字符串呢?答案是:Vue实例的watch
中写表达式的时候。那什么时候expOrFn
又是函数呢?在computed
的时候,还有在mount
的时候,将undateComponent
作为expOrFn
传入。
我们在初始化Watcher的时候会执行expOrFn
,会发生什么呢?其实会去获取data的对象的属性值,那么就会执行观察者的get()
,让我们再看一下get部分的代码:
get(){
if (Dep.target) {
dep.depend()
}
}
这里就收集了订阅者Watcher,同时也收集了依赖
依赖到底是什么?收集依赖又是什么?
Dep是data对象中的每一个属性数据,Watcher可以是一个updateComponent
模板,也可以是函数,也可以是表达式,无论是那种情况,都依赖data中的属性数据,也就是说,Watcher的依赖就是这些数据。
直接用一个例子来说:
new Vue({
data(){
return {
a: 1,
b: 2
}
},
computed: {
sum(){
return this.a + this.b
}
}
})
sum()
就是一个Watcher,this.a
,this.b
就是这个Watcher的Dep。
Dep.target为什么是唯一的
我们在收集订阅这的时候,需要知道这个依赖的订阅者是谁。
用一张图说明一下