Vue 之 Data 监听与更新
Object.defineProperty()
简单用法:
/* Object.defineProperty(obj, prop, descriptor) */
var o = {
a: 0
}
Object.defineProperty(o, 'b', {
get: function () {
console.log('get')
return 'get'
},
set: function (value) {
console.log(value)
},
configurable: true,
enumerable: true
})
例中
Object.defineProperty(o, 'b', {})
表示给o
添加一个叫b
的属性get
定义在读取o.b
时执行的方法,get
方法的执行结果为o.b
的值set
定义在给o.b
设置值时执行的方法configurable
表示o.b
是否可变及可被删除enumerable
表示o
的b
属性是否可以通过for-in
遍历获取到
简单说,我们可以通过Object.defineProperty
给一个对象添加一个属性,并且这个属性的值在被查询和被改变时都可以触发我们事先定义好的事件。
观察者模式
Vue实现观察者模式主要通过三个类:Observer类、Watcher类、Dep类
- Observer:通过
defineProperty
给数据添加get
和set
方法并在get
中收集依赖,在set
中通知更新 - Watcher:观察数据变化(接收
Dep
的notify
发出的通知),执行回调 - Dep:一个可观察对象,收集订阅(在
Observer
的get
时收集)、发送通知(在Observer
的set
时发布)
举例:
var app = new Vue({
el: '#J_app',
data: {
message: 'hello world'
},
mounted: function(){
console.log(this.message)
}
})
app.message = 'hellp vue'
console.log(`'app.message:' ${app.message}`)
例中:我们给data
添加了message
属性,然后,在数据初始化过程中:Observer
通过defineProperty
给message
设置get/set
属性,并在get
中调用Dep
的depend
方法,将message
添加为可观察对象,之后在Watcher
中,对message
进行求值,调用其get
方法,同时将Watcher
实例添加到可观察对象message
的观察者数组里;同时,在set
时,通过Dep
的notify
发布message
更新的事件,通知调用观察者数组中的update
方法。
在Vue中,上面流程中Observer
负责的部分是通过defineReactive$$1
实现的,可以简写如下:
/*
*参数含义:
*obj: 需要添加属性的对象
*key: 要添加的属性名
*value: 要添加的属性名对应的值
*customSetter: 在非生产环境,一些自定义的错误警告
*shallow: 属性值是否添加到代理到vm上
*/
function defineReactive(obj, key, value, customSetter, shallow) {
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
var getter = property && property.get;
var setter = property && property.set;
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function () {
var value = getter ? getter.call(obj) : val;
//收集依赖 交由Dep类负责
return value
},
set: function () {
var value = getter ? getter.call(obj) : val;
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
//开发环境属性检查并警告
if ("development" !== 'production' && customSetter) {
customSetter();
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
observe(newVal);
//发布更新消息
dep.notify();
}
})
}
这里的代码相对好理解,get
方法里的收集依赖其实也是一个曲折的过程。在watcher中,每添加一个新的watcher实例时,都会对相应的对象进行求值,也就是会主动触发一次defineReactive
的get
方法,然后就在get
方法里进行依赖收集。同时,数据有变更时,也会触发defineReactive
的set
方法,然后在set
方法里发布数据更新的消息dep.notify()
,然后dep.notify
里会对调相应的watcher进行update
。
Dep
类负责依赖收集和观察者存储,来看下Dep
类的代码:
/**
* 每个Dep的实例都是一个可观察对象,可以被多个观察者订阅
*/
var uid = 0;
var Dep = function Dep () {
this.id = uid++;
this.subs = [];
};
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
Dep.prototype.depend = function depend () {
if (Dep.target) {
Dep.target.addDep(this);
}
};
Dep.prototype.notify = function notify () {
// stabilize the subscriber list first
var subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null;
var targetStack = [];
function pushTarget (_target) {
if (Dep.target) { targetStack.push(Dep.target); }
Dep.target = _target;
}
function popTarget () {
Dep.target = targetStack.pop();
}
初始每个可观察对象dep
都有一个id和观察者数组(subs),可以对当前id的可观察对象:添加观察者(addSub
)、移除观察者(removeSub
)、设置为可观察对象(depend
)、通知变更到观察者们(notify
),添加和移除观察者相对简单,往数组push
和splice
即可;设置为可观察对象调用的是Dep.target.addDep(this)
,Dep.target
其实是Watcher
的实例,下节再看;通知变更到观察者们只是执行了是观察者们的update
方法,update
方法也会在下节Wather的实现一起看下。