一膳汪、發(fā)布/訂閱者模式
訂閱者把自己想訂閱的事件注冊到調(diào)度中心,當(dāng)該事件觸發(fā)時候九秀,發(fā)布者發(fā)布該事件到調(diào)度中心(順帶上下文)遗嗽,由調(diào)度中心統(tǒng)一調(diào)度訂閱者注冊到調(diào)度中心的處理代碼;如下圖
二、程序詳解(實現(xiàn)$watcher方法)
一共三個js文件:
-
index.js
:用來遍歷數(shù)據(jù)鼓蜒,并在數(shù)據(jù)對象的每個屬性上添加getter
和setter
,當(dāng)有數(shù)據(jù)變動的時候給通道發(fā)送一個notify
-
dep.js
:通道痹换,用來連接發(fā)布者和訂閱者,有一個數(shù)組變量通過addSub方法
來存放watcher
都弹,當(dāng)通道收到notify
之后遍歷數(shù)組觸發(fā)每個watcher
的update
-
watcher.js
:充當(dāng)訂閱者娇豫,獲取更新后的數(shù)據(jù)并且執(zhí)行回調(diào)函數(shù)
watcher.js:
// Watcher的實例就是訂閱者
function Watcher(vm,exp,cb){
console.log("watcher執(zhí)行了");
this.cb = cb;
this.vm = vm;
this.exp = exp
this.value = this.get()//更新前的值
}
var w = Watcher.prototype;
// 訂閱者的更新方法
w.update = function (){
var value = this.get() //這里是更新后的值
if(value!==this.value){
this.value = value //用新值覆蓋舊值
this.cb.call(this.vm,value)
}
}
// 通過Watcher的實例調(diào)用了getter
w.get = function (){
Dep.target = this//表明是watcher調(diào)用了getter
return this.vm.data[this.exp] //這里會調(diào)用get方法
}
這里有兩個小問題:
- 如何把watcher加入到數(shù)組里;
- 如何判斷是watcher觸發(fā)的
getter
還是普通觸發(fā)getter
解決辦法:
在dep.js中設(shè)置一個全局變量畅厢,如果是watcher觸發(fā)的getter就把這個watcher實例賦值給這個變量冯痢,然后在index.js中的getter
里進(jìn)行判斷,如果全局變量不為null
,則把watcher實例添加到數(shù)組框杜;
dep.js代碼如下:
// 定義一個Dep(調(diào)度中心)浦楣,用來維護(hù)一系列觀察者,方便添加觀察者
function Dep(){
this.subs = [] //存放訂閱者的數(shù)組
}
var s = Dep.prototype;
// 把訂閱者都存到數(shù)組里面
s.addSub = function (sub){
this.subs.push(sub)
}
// 訂閱者想訂閱的事件咪辱,注冊到事件中心
s.notify = function (){
// 一旦調(diào)用了set就觸發(fā)notify,然后遍歷每個觀察者振劳,并觸發(fā)他們相應(yīng)的update方法
this.subs.forEach(function (sub){
sub.update();
/*
sub.update():
調(diào)度中心統(tǒng)一調(diào)度訂閱者注冊到調(diào)度中心的處理代碼
*/
})
}
Dep.target = null;//定義一個全局變量,用來判斷是否是watcher調(diào)用了getter
index.js代碼如下:
// 發(fā)布者油狂,對data做監(jiān)聽历恐,提供了某個數(shù)據(jù)項變化的能力
function Observer(data){
this.data = data;
this.walk(data)
}
// 將原型賦值給一個變量
var p = Observer.prototype;
// 遍歷對象所有屬性寸癌,包括子屬性
p.walk = function (obj){
var _this = this;
Object.keys(obj).forEach(function (key){
_this.observer(obj[key])
_this.convert(key,obj[key])
})
}
// 綁定getter 和setter
p.convert = function (key,val){
//每次set函數(shù)調(diào)用的時候,觸發(fā)notify
var dep = new Dep() //發(fā)布給訂閱者
var _this = this;
Object.defineProperty(this.data,key,{
configurable:true,
enumarable:true,
get:function (){
console.log("你訪問了"+key);
// Watcher的實例調(diào)用了getter弱贼,將watcher加入到調(diào)度中心的數(shù)組里面
if(Dep.target){
dep.addSub(Dep.target)
}
return val
},
set:function (newVal){
// 如果新設(shè)置的值和原來相等則不重新賦值
if(newVal==val){
return
}
console.log("你設(shè)置了"+key);
console.log("新的"+key+"="+newVal);
val = newVal
// 如果設(shè)置的新值是一個對象蒸苇,則遞歸它,加上set/get
_this.observer(newVal)
dep.notify()//發(fā)布者發(fā)布到訂閱中心
}
})
}
// 判斷屬性值是否是一個對象,如果是再深度監(jiān)聽
p.observer = function (val){
if(typeof val ==="object"){
new Observer(val)
}
}
// 定義一個watcher
p.$watcher = function (exp,cb){
new Watcher(this,exp,cb)
}
let data = {
name:'dailu',
age:25
};
var app = new Observer(data);
app.$watcher('age', function(age) {
console.log(`我的年紀(jì)變了哮洽,現(xiàn)在已經(jīng)是:${age}歲了`)
});
app.data.age = 100;
// console.log(app.data.name);