ife.baidu筆記 | 動態(tài)數(shù)據(jù)綁定(二)

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

Awesome Vuejs.png

動態(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)行有效訪問:

無法對“比較深”的對象的進(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)存空間(不要忘了,dataobj都是引用類型,它們的值都是指向同一個(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.definePropertygetter/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ā)布-訂閱模式

觀察者模式又叫做發(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")

例1

再看看例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")
例2

當(dāng)然,訂閱者還會擁有取消訂閱功能,以及在事件發(fā)生后不同訂閱者可以有不同反應(yīng)(或者有參數(shù)傳入)。
因此,可以總結(jié)出發(fā)布-訂閱模式的流程:

  1. 訂閱者訂閱某個(gè)主題(或者關(guān)注某個(gè)發(fā)布者)
  2. 某個(gè)主題(某個(gè)發(fā)布者)將該訂閱者記錄進(jìn)待通知名單
  3. 在某個(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
Test Case
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ì)(?!?⌒)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容