你踩過的坑會幫助你日后走得更快

動態(tài)數(shù)據(jù)綁定(二)
- <a >題目</a>
- <a >任務(wù)源碼</a>
- 考察知識點(diǎn):
遞歸Recursion
發(fā)布-訂閱模式
任務(wù)二主要涉及兩大塊:訪問引用類型的屬性,利用發(fā)布-訂閱模式實(shí)現(xiàn)事件監(jiān)聽。
先講講如何解決“比較深”的屬性
看到任務(wù)二,發(fā)現(xiàn)了在<a href="http://www.itdecent.cn/p/3fb7c2a6b047">任務(wù)一</a>寫的代碼有一點(diǎn)問題,就是無法對“比較深”的對象的進(jìn)行有效訪問:
在<a >任務(wù)一</a>的代碼中,為了使實(shí)例通過person1.data.進(jìn)行屬性訪問,新建了原型對象屬性Observer.prototype.data = {},但是當(dāng)傳入?yún)?shù)對象是一個(gè)“比較深”的對象(屬性值也是對象),就無法建立如例子中的person1.data.address.add1屬性訪問,因此<a >任務(wù)一</a>的代碼需要重構(gòu)。踩坑,會沉沒一點(diǎn)時(shí)間成本,但也是一個(gè)自我發(fā)現(xiàn)和進(jìn)步的機(jī)會。你踩過的坑會幫助你日后走得更快。
在<a >任務(wù)二</a>中,關(guān)鍵的是在function函數(shù)中定義this.data = obj;令每個(gè)實(shí)例對象的data與傳入的對象obj指向相同的內(nèi)存空間(不要忘了,data與obj都是引用類型,它們的值都是指向同一個(gè)地址的"指針")。
當(dāng)遍歷到較深的屬性時(shí),利用遞歸new Observer(obj[key])實(shí)現(xiàn)較深的屬性的getter/setter定義。同時(shí),在對象實(shí)例設(shè)置新的值是一個(gè)對象的時(shí)候,也是利用遞歸new Observer(newValue)對新增的"較深"屬性定義getter/setter響應(yīng)。
不多說,上代碼,只是<a >任務(wù)一</a>的修正和拓展。
function Observer(obj){
this.data = obj;
this.walk(obj);
}
Observer.prototype.walk = function(obj) {
Object.keys(obj).forEach(key => {
let val = obj[key]
console.log(key)
if(typeof obj[key] === "object"){
new Observer(obj[key])
}
Object.defineProperty(this.data,key,{
enumerable: true,
configurable: true,
get:function(){
console.log("You are visiting the attribute: "+ key +" - " + val)
return val },
set:function(newValue) {
if(typeof newValue === 'object'){
new Observer(newValue)
}
console.log("You are updating the attribute: "+ key +" - "+ newValue)
val = newValue
}
})
})
}
#######~~(╯﹏╰)b一不小心又是自己挖的坑
在寫Object.defineProperty的getter/setter的時(shí)候,遇到了一個(gè)Uncaught RangeError: Maximum call stack size exceeded,其中代碼是這樣的
Object.defineProperty(this.data,key,{
get:function(){
console.log("You are visiting the attribute: "+ key +" - " + obj[key])
return obj[key] },
....
}
在getter里訪問obj[key]就相當(dāng)于陷入死循環(huán)無限調(diào)用get()方法,直到超過最大的棧數(shù)。
解決了任務(wù)二中的問題一和問題二,
現(xiàn)在了解發(fā)布-訂閱模式

觀察者模式又叫做發(fā)布-訂閱模式,它定義了一種一對多的關(guān)系,讓多個(gè)觀察者對象同時(shí)監(jiān)聽某一個(gè)主題對象,這個(gè)主題對象的狀態(tài)發(fā)生改變時(shí)就會通知所有觀察著對象。
看完定義有點(diǎn)懵逼,我把發(fā)布-訂閱模式定義為以下三點(diǎn):
- 發(fā)布-訂閱模式由兩個(gè)角色組成:(事件)發(fā)布者和訂閱者。
- 訂閱者向(事件)發(fā)布者進(jìn)行注冊(訂閱),在事件發(fā)布時(shí)被通知。
- 發(fā)布者在事件發(fā)生時(shí)向(之前在他這里進(jìn)行注冊/訂閱的)訂閱者發(fā)送通知。
這個(gè)情景是不是似曾相識?其實(shí)存在于我們生活的方方面面。
例如訂閱周刊
小明(訂閱者)對前端周刊(主題/發(fā)布者)有興趣,于是交錢訂閱每期的前端周刊(訂閱/注冊事件),并計(jì)劃把每期收到的前端周刊(事件發(fā)布)帶回學(xué)校閱讀(訂閱者在事件發(fā)生后所進(jìn)行的動作)。
小紅(訂閱者)也對前端周刊(主題/發(fā)布者)有興趣,于是也交錢訂閱每期的前端周刊(訂閱/注冊事件),但小紅只是想收藏書籍,所以就把每期收到的前端周刊(事件發(fā)布)放到家里書柜(訂閱者在事件發(fā)生后所進(jìn)行的動作)。
例如Github上的watch按鈕
小明(訂閱者)在github上建立了一個(gè)開源項(xiàng)目,他想在有人start了他的項(xiàng)目/有人向他提出issue/有人pull request的時(shí)候(主題/發(fā)布者)及時(shí)收到郵件通知,于是他點(diǎn)擊了watch按鈕(訂閱/注冊事件)進(jìn)行通郵件知設(shè)置,以便他及時(shí)處理issue/PR(訂閱者在事件發(fā)生后所進(jìn)行的動作)。
代碼如下:
// 主題發(fā)布者
function Publisher() {
this.subscribers = {};
// 用于存儲某事件對應(yīng)的訂閱者列表....
}
// 添加一個(gè)發(fā)布功能(方法),在事件發(fā)生時(shí)通知訂閱者名單里的每一個(gè)人
Publisher.prototype.publish = function(eventType) {
if(eventType in this.subscribers){
this.subscribers[eventType].forEach(function(subscriberCb){
subscriberCb();
})
}
}
// 觀察者/訂閱者
function Observer() {
}
// 訂閱者有訂閱/注冊能力
Observer.prototype.subscribe = (publisher,eventType,cb) =>{
if(!publisher.subscribers.hasOwnProperty(eventType)){
publisher.subscribers[eventType] = []
}
publisher.subscribers[eventType].push(cb)
}
測試
// 實(shí)例化
// 例1.
var techbook_Publisher = new Publisher()
var xiaoming = new Observer()
var xiaohong = new Observer()
//讀者進(jìn)行訂閱/注冊
xiaoming.subscribe(techbook_Publisher,"web-book",function(){
console.log("小明要把期刊帶回學(xué)校閱讀")
})
xiaohong.subscribe(techbook_Publisher,"web-book",function(){
console.log("小紅要把期刊在家里收藏")
})
//出版社出版期刊,事件發(fā)生
techbook_Publisher.publish("web-book")
再看看例2
// 例2.
var github = new Publisher()
var xiaoming = new Observer()
//用戶對該倉庫的動態(tài)進(jìn)行訂閱/注冊
xiaoming.subscribe(github,"watch-this-repo",function(){console.log("小明的github-repo在有人提issue/PR時(shí)收到郵件通知")})
github.publish("watch-this-repo")
當(dāng)然,訂閱者還會擁有取消訂閱功能,以及在事件發(fā)生后不同訂閱者可以有不同反應(yīng)(或者有參數(shù)傳入)。
因此,可以總結(jié)出發(fā)布-訂閱模式的流程:
- 訂閱者訂閱某個(gè)主題(或者關(guān)注某個(gè)發(fā)布者)
- 某個(gè)主題(某個(gè)發(fā)布者)將該訂閱者記錄進(jìn)待通知名單
- 在某個(gè)情景下主題發(fā)布,通知在待通知名單上的各個(gè)讀者,各個(gè)讀者進(jìn)行各自的后續(xù)操作。
回歸到<a >任務(wù)二</a>,
實(shí)現(xiàn)訂閱者的訂閱功能Observer.prototype.$watch以及某時(shí)刻事件觸發(fā)(發(fā)布)功能Observer.prototype.$change,并在對象實(shí)例屬性值變化的時(shí)候調(diào)用Observer.prototype.$change。
Observer.prototype.oberseredList = {} //subscriber list,shared property with oberser&publisher
let oberseredList = Observer.prototype.oberseredList
Observer.prototype.$watch = (oberseredKey,cb) =>{ //subscriber register
if(!oberseredList.hasOwnProperty(oberseredKey)){
oberseredList[oberseredKey] = []
}
oberseredList[oberseredKey].push(cb)
}
Observer.prototype.$change = function(oberseredKey,newValue){ //Event Trigger
let params = Array.prototype.slice.call(arguments,1)
if(oberseredList.hasOwnProperty(oberseredKey)) {
oberseredList[oberseredKey].forEach(cb => {
cb.apply(null,params)
})
}
}
還有關(guān)鍵在set里調(diào)用Observer.prototype.$change。
set:function(newValue) {
......
Observer.prototype.$change.call(this,key,newValue) //Event Trigger
....
}
/*Test Case*/
var person1 = new Observer({name:"xiaoming", age:20,
address:{add1:"China",add2:"UK"} });
person1.data.age
person1.$watch('age', function(age) {
console.log(`我的年紀(jì)變了,現(xiàn)在已經(jīng)是:${age}歲了`)
});
person1.data.age = 55
Reference
系列目錄
<a href="http://www.itdecent.cn/p/3fb7c2a6b047">ife.baidu筆記 | 動態(tài)數(shù)據(jù)綁定(一)</a>
原創(chuàng)文章
簡書:<a href="http://www.itdecent.cn/u/c0600377679d">HelloCherry</a>
Github: <a >CaiYiLiang</a>
Girhub / vue-demos:
- <a >利用vue.js實(shí)現(xiàn)簡易計(jì)算器</a>
- <a >實(shí)現(xiàn)簡單的單頁面應(yīng)用(vue2.0,vue-router,vue-cliand ajax(jsonp))</a>
- <a >利用vue.js,vuex,vue-router和 Element UI實(shí)現(xiàn)購物車場景</a>
很高興寫的<a >vue demos</a>被收錄到 <a >awesome-vue</a>中,簡直就是一朵小紅花??
如果覺得有一點(diǎn)點(diǎn)幫助,一個(gè)??就是鼓勵(lì)(?!?⌒)