通过前面对数据的劫持,我们可以在getter和setter方法内使用发布-订阅的模式来监测数据的变化并调用回调函数。
实现方式主要是通过建立Dep类和Watcher类,通过实例化Dep类对象分别对每个监听的变量的回调方法进行收集,从而在setter方法(即改变值时调用Dep实例对象),调用各自watcher的回调函数。
目标:实现两个监听方法watch和$watch
var vm = new Vue({
data: {
message: 'nihao',
person: {
name: 'zs'
}
},
watch: {
message() {
console.log('message发生了变化1');
}
}
})
vm.$watch('message',()=>{
console.log('message发生了变化2');
})
Watcher类
Watcher类其实就是观察者,如果某个变量被定义了监听方法时,就会去new一个Watcher
class Watcher {
constructor(vm, exp, cb) {
this.vm = vm; // 实例对象
this.exp = exp; // 属性
this.cb = cb; // 回调函数
this.get();
}
//求值
get() {
Dep.target = this; // 把当前实例对象直接挂载到Dep类上
this.vm[this.exp]; // 这里会读取exp属性,从调用getter函数,通过后面在getter中调用dep.depend会把这个实例化对象push到subs中
Dep.target = null; // 收集到实例对象后就可以清除了
}
run() {
this.cb.call(this.vm);
}
}
Dep类
Dep类定义了depend方法,当读取属性时调用getter收集watcher(对应以上this.vm[this.exp]);定义了notify方法,当设置属性时调用setter来执行当前属性的监听watcher
class Dep {
constructor() {
this.subs = [];
}
depend() {
if(Dep.target) {
this.subs.push(Dep.target);
console.log(this.subs);
}
}
notify() {
this.subs.forEach((watcher)=>{
//依次执行回调函数
watcher.run();
});
}
}
1.实现$watch
显然,$watch方法是挂载在Vue的实例对象vm上的,所以需要在Vue类中进行定义
$watch(key, cb) {
new Watcher(this, key, cb);
}
2.实现watch
因为是在实例化对象时就定义好的,说明实例化时就已经完成了watch的收集,需要在Vue类中直接定义并调用initWatch
initWatch() {
let watch = this.$options.watch;
if(watch) {
let keys = Object.keys(watch);
for(let i = 0; i < keys.length; i++) {
new Watcher(this, keys[i],watch[keys[i]]);
}
}
}
完整实现过程:
class Vue {
constructor(options) {
this._data = options.data;
this.$options = options;
this.initData();
}
initData() {
let data = this._data;
let keys = Object.keys(data);
for(let i = 0; i < keys.length; i++) {
let dep = new Dep();
Object.defineProperty(this, keys[i],{
enumerable: true,
configurable: true,
get: function proxyGetter() {
dep.depend();
return data[keys[i]];
},
set: function proxySetter(val) {
dep.notify();
data[keys[i]] = val;
}
});
observe(data[keys[i]]);
}
this.initWatch();
}
initWatch() {
let watch = this.$options.watch;
if(watch) {
let keys = Object.keys(watch);
for(let i = 0; i < keys.length; i++) {
new Watcher(this, keys[i],watch[keys[i]]);
}
}
}
$watch(key, cb) {
new Watcher(this, key, cb);
}
}
function observe(data) {
let type = Object.prototype.toString.call(data);
if(type != '[object Object]' && type != '[object Array]') {
return;
}
new Observer(data);
}
function defineReactive(obj, key, value) {
observe(obj[key]);
let dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function proxyGetter() {
dep.depend();
return value;
},
set: function proxySetter(val) {
if(val == value) return;
dep.notify();
value = val;
}
})
}
class Observer {
constructor(data) {
this.walk(data);
}
walk(data) {
let keys = Object.keys(data);
for(let i = 0; i < keys.length; i++) {
defineReactive(data,keys[i],data[keys[i]]);
}
}
}
class Dep {
constructor() {
this.subs = [];
}
depend() {
if(Dep.target) {
this.subs.push(Dep.target);
console.log(this.subs);
}
}
notify() {
this.subs.forEach((watcher)=>{
//依次执行回调函数
watcher.run();
});
}
}
class Watcher {
constructor(vm, exp, cb) {
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.get();
}
//求值
get() {
Dep.target = this;
this.vm[this.exp];
Dep.target = null;
}
run() {
this.cb.call(this.vm);
}
}
var vm = new Vue({
data: {
message: 'nihao',
person: {
name: 'zs'
}
},
watch: {
message() {
console.log('message发生了变化1');
}
}
})
vm.$watch('message',()=>{
console.log('message发生了变化2');
})