【設(shè)計(jì)模式】觀察者模式 和 發(fā)布訂閱模式

目錄:
觀察者模式
發(fā)布-訂閱模式
觀察者模式和發(fā)布訂閱模式的區(qū)別
實(shí)現(xiàn)vue數(shù)據(jù)雙向綁定

(1)觀察者模式

(1)概念
  • 對(duì)程序中的某個(gè)對(duì)象的狀態(tài)進(jìn)行觀察殃恒,并且在其發(fā)生改變的時(shí)候得到通知
  • 存在(觀察者)和(目標(biāo))兩種角色
(2)subject對(duì)象 和 observer對(duì)象
  • 目標(biāo)對(duì)象 : subject
  • 觀察者對(duì)象:observer
  • 在目標(biāo)對(duì)象中存放觀察者對(duì)象的引用
  • 一個(gè)目標(biāo)對(duì)象對(duì)應(yīng)多個(gè)觀察者對(duì)象(一對(duì)多)
(3)在觀察者模式中,目標(biāo)對(duì)象與觀察者對(duì)象堤器,相互獨(dú)立又相互聯(lián)系
  • 兩者都是相互獨(dú)立的對(duì)象個(gè)體
  • 觀察者對(duì)象在目標(biāo)對(duì)象中訂閱事件,目標(biāo)對(duì)象廣播發(fā)布事件
  • pattern:是模式的意思 (design pattern設(shè)計(jì)模式)
  • notify:通知
  • Subject:擁有添加溜徙,刪除,通知(發(fā)布)等一系列觀察者對(duì)象Observer的方法
  • Observer:擁有更新方法等
(4)ES5 代碼實(shí)現(xiàn)過程
觀察者模式 - ES5代碼實(shí)現(xiàn)過程

1. 新建兩個(gè)對(duì)象
- 目標(biāo)對(duì)象:------ subject
- 觀察者對(duì)象:---- observer
2. 在(目標(biāo)對(duì)象)中存放(觀察者對(duì)象)的引用
function Subject() { // 目標(biāo)對(duì)象
  this.observers = new ObserverList()
}
function ObserverList() {
  this.observerList = []
}
function Observer() {} // 觀察者對(duì)象       -- 函數(shù)都是Function構(gòu)造函數(shù)的實(shí)例伶选,即函數(shù)也是一個(gè)對(duì)象


3. 對(duì)于目標(biāo)對(duì)象中的引用隐岛,我們必須可以動(dòng)態(tài)的控制:
Subject:擁有添加猫妙,刪除,通知(發(fā)布)等一系列觀察者對(duì)象Observer的方法
Observer:擁有更新方法等
- add(obj) 添加訂閱者對(duì)象
- count() 訂閱者對(duì)象總數(shù)
- get(index) 獲取某個(gè)訂閱者對(duì)象
- indexOf(obj, startIndex) 某個(gè)訂閱者對(duì)象的位置
- removeAt(index) 刪除某個(gè)訂閱者對(duì)象
- addObserver
- removeObserver
4. 目標(biāo)對(duì)象廣播發(fā)布消息
- 注意:
  - 目標(biāo)對(duì)象并不能指定觀察者對(duì)象做出什么相應(yīng)的變化聚凹,目標(biāo)對(duì)象只有通知的作用
  - 我們將具體的觀察者對(duì)象該作出的變化交給了觀察者對(duì)象自己去處理
  - 即 觀察者對(duì)象擁有自己的 update(context) 方法來作出改變
  - 同時(shí)該方法不應(yīng)該寫在原型鏈上割坠,因?yàn)槊恳粋€(gè)實(shí)例化后的 Observer 對(duì)象所做的響應(yīng)都是不同的,需要獨(dú)立存儲(chǔ) update(context)方法
Subject.prototype.notify = function(context) { // 目標(biāo)對(duì)象發(fā)布消息事件
  var observerCount = this.observers.count() 
  for (var i = 0; i < observerCount; i++) {
    this.observers.get(i).update(context)
   // 取出所有訂閱了目標(biāo)對(duì)象的觀察者對(duì)象妒牙,并執(zhí)行他們各自的收到消息后的更新方法
   // 注意每個(gè)觀察者對(duì)象的update()方法都是定義在自身的實(shí)例上的彼哼,各個(gè)觀察者之間互不影響
  }
}
function Observer() {
  this.update = function() {
    // ...
  };
}






--------------
5. 完整代碼

function ObserverList() { -------------------- 目標(biāo)對(duì)象的引用
  this.observerList = [];
}
ObserverList.prototype = {
  add: function(obj) { // 添加觀察者,方法掛載在ObserverList的原型連鏈上
    return this.observerList.push(obj); // this指向的是調(diào)用add方法時(shí)所在的對(duì)象湘今,這里是  ObserverList的實(shí)例在調(diào)用
  },
  count: function() { // 總數(shù)量
    return this.observerList.length;
  },
  get: function(index) { // 獲取某個(gè)觀察者
    if (index > -1 && index < this.observerList.length) {
      return this.observerList[index];
    }
  },
  indexOf: function(obj, startIndex) { // 獲取某個(gè)觀察者對(duì)象的下標(biāo)
    // obj某個(gè)觀察者對(duì)象
    // startIndex 開始搜索的起始位置
    var i = startIndex;
    while (i < this.observerList.length) {
      if (this.observerList[i] === obj) {
        return i;
      }
      i++;
    }
    return -1; // 找到返回下標(biāo)敢朱,沒找到返回-1
  },
  removeAt: function(index) { // 刪除某個(gè)觀察者對(duì)象
    this.observerList.splice(index, 1);
  }
}


function Subject() { -------------------------------- 目標(biāo)對(duì)象
  this.observers = new ObserverList(); // 實(shí)例化
}
Subject.prototype = { // 目標(biāo)對(duì)象上包含添加,刪除摩瞎,通知觀察者對(duì)象的方法
  addObserver: function(observer) {
    this.observers.add(observer); // --------------------------------調(diào)用ObserverList原型上的add方法
  },
  removeObserver: function(observer) {
    this.observers.removeAt(this.observers.indexOf(observer, 0)); // 調(diào)用ObserverList原型上的removeAt方法
  },
  notify: function(context) { // ---------------- 目標(biāo)對(duì)象發(fā)布消息
    var observerCount = this.observers.count();
    for (var i = 0; i < observerCount; i++) {
      this.observers.get(i).update(context);
    }
  },
}


function Observer(content) {  ------------------------- 觀察者對(duì)象
  this.update = function() {
    console.log(content)
  }
}
const observer1 = new Observer('111')
const observer2 = new Observer('222') // 觀察者對(duì)象中一般掛載收到通知后的 更新方法
const subject = new Subject()
subject.observers.add(observer1)
subject.observers.add(observer2)
subject.notify() // 目標(biāo)對(duì)象上一般都掛載添加拴签,刪除,通知 觀察者對(duì)象的方法
觀察者模式

詳細(xì) https://juejin.im/post/5cc57704e51d456e5a072975

精簡 https://juejin.im/post/5bb1bb616fb9a05d2b6dccfa

實(shí)現(xiàn)vue數(shù)據(jù)雙向綁定 https://juejin.im/post/5bce9a35f265da0abd355715

(5)ES6 代碼實(shí)現(xiàn)過程
// 觀察者模式 es6
// 目標(biāo)類
class Subject {
  constructor() {
    this.observers = []
  }
  // 添加
  add = (obj) => {
    this.observers.push(obj)
  }
  // 刪除
  remove(index) {
    if (index > -1 && index < this.observers.length) {
      this.observers.splice(index, 1)
    }
  }
  // 通知(發(fā)布消息)
  notify() {
    for(let i = 0; i < this.observers.length; i++) {
      this.observers[i].update()
    }
  }
}
// 觀察者類
class Observer {
  constructor(content) {
    this.update = () => {
      console.log(content)
    }
  }
}
const subject = new Subject()
const observer1 = new Observer('111')
const observer2 = new Observer('222')
subject.add(observer1)
subject.add(observer2)
subject.notify()
splice()
- splice()用于刪除數(shù)組的一部分成員旗们,并可以在刪除的位置添加新的數(shù)組成員蚓哩,返回值是刪除的元素
- 返回值:刪除的元素組成的數(shù)組
- 參數(shù):
  - 第一個(gè)參數(shù):刪除的起始位置,默認(rèn)從0開始
  - 第二個(gè)參數(shù):被刪除的元素個(gè)數(shù)
  - 如果有更多的參數(shù):表示要插入數(shù)組的元素
- 該方法改變?cè)瓟?shù)組
const arr = [1, 2, 3]
const res = arr.splice(1, 2, 222, 333) // 從下標(biāo)為1的位置開始刪除2個(gè)元素上渴,并將222杖剪,333插入到被刪除的位置之后
console.log(res) // [2,3] 返回值是被刪除的元素組成的數(shù)組
console.log(arr) // [1, 222, 333]改變?cè)瓟?shù)組
for of
for in

1. 數(shù)組
- for(let i of arr)  ----------- i表示值
- for(let i in arr)  ----------- i 表示下標(biāo)
2. 對(duì)象
- 對(duì)象只能用for in循環(huán),不能用for of循環(huán)
- 因?yàn)閷?duì)象沒有部署iterator接口驰贷,不用使用for of循環(huán)
- for (let i in obj) ----------- i表示下標(biāo)

http://www.reibang.com/p/3e3451708143









(2)發(fā)布訂閱模式

(1)角色
  • 發(fā)布者:Publisher
  • 訂閱者:Subscriber
  • 中介:Topic/Event Channel
  • 中介既要接收發(fā)布者所發(fā)布的消息事件盛嘿,又要將消息派發(fā)給訂閱者
  • 所以中介要根據(jù)不同的事件,儲(chǔ)存相應(yīng)的訂閱者信息
  • 通過中介對(duì)象括袒,完全解偶了發(fā)布者和訂閱者
(2)實(shí)例
1. 創(chuàng)建中介對(duì)象 ------------------------------ pubsub

2. 給中介對(duì)象的每個(gè)訂閱者對(duì)象一個(gè)標(biāo)識(shí) ---------- subUid
  - 每當(dāng)有一個(gè)新的訂閱者對(duì)象 訂閱事件的時(shí)候次兆,就給新的訂閱者對(duì)象一個(gè) subUid

3. topics對(duì)象的結(jié)構(gòu):
  - key:  對(duì)應(yīng)不同的事件名稱
  - value:是一個(gè)數(shù)組,每個(gè)成員是一個(gè)對(duì)象锹锰,存放訂閱該事件的(訂閱者對(duì)象)及發(fā)生事件之后作出的(響應(yīng))





4. 完整代碼:
  // 發(fā)布-訂閱模式
  var pubsub = {} // 中介
  (function(myObject) {
  var topics = {} // 存放(訂閱了某事件)對(duì)應(yīng)的 (訂閱者對(duì)象數(shù)組)
  var subUid = -1 // 每個(gè)訂閱者的唯一ID

  // 發(fā)布
  myObject.publish = function(topic, args) {
    if (!topics[topic]) {
      return false
    }
    var subscribes = topics[topic]
    var len = subscribes ? subscribes.length : 0
    for (let i = len; i >= 0; i--) {
      subscribes[i].func(args)
    }
    // 上面的for循環(huán)芥炭,也可以用for of 實(shí)現(xiàn)
    // for(let obj of topics[topic]) {
    //   obj.func.call(this, args)
    // }
    return this // 這里可以不return this,不需要鏈?zhǔn)秸{(diào)用
  }
 
 // 訂閱
  myObject.subscribe = function(topic, func) {
  // topic:指訂閱的事件名稱
  // func: 訂閱者對(duì)象收到消息通知后的響應(yīng)事件
  if (!topics[topic]) {
    // 如果事件不存在恃慧,則新建該事件园蝠,值是數(shù)組,數(shù)組中存放訂閱該事件的 訂閱者對(duì)象相關(guān)信息
    // 相關(guān)信息包括
      // 1. token -----訂閱者對(duì)象的唯一標(biāo)識(shí)符ID
      // 2. func ------發(fā)生該事件(訂閱了該事件的訂閱者對(duì)象痢士,收到發(fā)布的該事件的通知時(shí)彪薛,執(zhí)行的響應(yīng)函數(shù))
    topics[topic] = []
  }
  topics[topic].push({
    token: (++subUid).toString(),
    func: func // 這里可以簡寫
  })
  // 返回訂閱者對(duì)象的唯一標(biāo)識(shí),用于取消訂閱時(shí)是根據(jù)token來刪除該訂閱者對(duì)象
  return token
  }

  // 取消訂閱
  myObject.unsubscribe = function(token) {
   // 利用token刪除該事件對(duì)應(yīng)的 訂閱者對(duì)象
    for (let j in topics) {
      if (topics[j]) {
        for(let i = 0; i < topics[j].length; i++) {
          if (topics[j][i].token === token) {
            topics[j].splice(i, 1)
            return token
          }
        }
      }
    }
    return this // 可以不return this
  }
})(pubsub)



--------------
1. 多個(gè)訂閱者對(duì)象,訂閱了同一個(gè)事件 go 事件
pubsub.subscribe('go', function(args) {
  console.log(args)
})
pubsub.subscribe('go', function(args) {
  console.log(args + 'oher subscriber')
})
pubsub.publish('go', 'home')



--------------
2. 取消訂閱善延,發(fā)布的go事件少态,將不會(huì)觸發(fā) 訂閱者的響應(yīng)函數(shù)
pubsub.subscribe('go', function(args) {
  console.log(args)
})
pubsub.unsubscribe('0')
pubsub.publish('go', 'home')
發(fā)布-訂閱模式

(3)觀察者模式,發(fā)布-訂閱模式的區(qū)別和聯(lián)系

(1)區(qū)別
  • 觀察者模式:需要觀察者自己定義事件發(fā)生時(shí)的響應(yīng)函數(shù)
  • 發(fā)布-訂閱模式:在(發(fā)布者對(duì)象)易遣,和(訂閱者對(duì)象)之間彼妻,增加了(中介對(duì)象)
(2)聯(lián)系
  • 二者都降低了代碼的(耦合性)
  • 都具有消息傳遞的機(jī)制,以(數(shù)據(jù)為中心)的設(shè)計(jì)思想

(4)vue數(shù)據(jù)雙向綁定

  • directive:指令
  • compile:編譯
前置知識(shí):

1. Element.children
- 返回一個(gè)類似數(shù)組的對(duì)象(HTMLCollection實(shí)例)
- 包括當(dāng)前元素節(jié)點(diǎn)的所有子元素
- 如果當(dāng)前元素沒有子元素豆茫,則返回的對(duì)象包含0個(gè)成員

2. Node.childNodes
- 返回一個(gè)類似數(shù)組的對(duì)象(NodeList集合)侨歉,成員包括當(dāng)前節(jié)點(diǎn)的所有子節(jié)點(diǎn)
- NodeList是一個(gè)動(dòng)態(tài)集合

3. Node.childNodes 和 Element.children 的區(qū)別
- Element.children只包含元素類型的子節(jié)點(diǎn),不包含其他類型的子節(jié)點(diǎn)


-----
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="app">
        <input type="text" v-model="name"  id="text">
        <div v-text="name"></div>
    </div>
    <script>
        // const tex = document.getElementById('text')
        // tex.addEventListener('input', function() {
        //     console.log('input')
        // })
    </script>
    <script>
        class Watcher {
            constructor(name, el, vm, exp, attr) {
                this.name = name
                this.el = el
                this.vm = vm
                this.exp = exp
                this.attr = attr

                this._update()
            }
            _update() {
                this.el[this.attr] = this.vm.$data[this.exp]
            }
        }
        class MyVue {
            constructor(options) {
                this.option = options
                this.$el = document.querySelector(options.el)
                this.$data = options.data

                this._directive = {}
                this._observes(this.$data)
                this._compile(this.$el)
            }
            // 數(shù)據(jù)攔截
            // get set 獲取和重寫
            // 并發(fā)布通知
            _observes(data) {
               let val;
               for (let key in data) {
                   if ( data.hasOwnProperty(key) ) {
                       this._directive[key] = []
                   }
                   val = data[key]
                   // val 可能是對(duì)象揩魂,和數(shù)組
                   if (typeof val === 'object') {
                       this._observes(val)
                   }
                   let _dir = this._directive[key]
                   Object.defineProperty(this.$data, key, {
                       enumerable: true,
                       configurable: true,
                       get() {
                           return val
                       },
                       set(newValue) {
                           if (val !== newValue) {
                               val = newValue
                               _dir.forEach(item => item._update())
                           }
                       }
                   })
               }
            }
            _compile(el) {
                // 子元素
                let nodes = el.children
                for (let i in Array.from(nodes)) {
                    const node = nodes[i]
                    console.log(node, 'xxx')
                    if (node.children.length) {
                        this._compile(node)
                    }
                    if (node.hasAttribute('v-text')) {
                        const attrValue = node.getAttribute('v-text')
                        this._directive[attrValue].push(new Watcher('text', node, this, attrValue, 'innerHTML'))
                    }
                    if (node.hasAttribute('v-model') && ( node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {
                        let _this = this

                        node.addEventListener('input', (function() {
                            let attrValue = node.getAttribute('v-model')
                            _this._directive[attrValue].push(new Watcher('input', node, _this, attrValue, 'value'))

                            return function() {
                                console.log(typeof attrValue)
                                _this.$data[attrValue] = node.value
                            }
                        })())
                    }
                }
            }
        }
        const ins = new MyVue({
            el: '#app',
            data: {
                name: 'wang'
            }
        })
    </script>
</body>
</html>

實(shí)現(xiàn)vue數(shù)據(jù)雙向綁定 https://juejin.im/post/5bce9a35f265da0abd355715

https://segmentfault.com/a/1190000016789934

https://juejin.im/post/5c10edc36fb9a049e307f67a

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末幽邓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子肤京,更是在濱河造成了極大的恐慌颊艳,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忘分,死亡現(xiàn)場離奇詭異棋枕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)妒峦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門重斑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肯骇,你說我怎么就攤上這事窥浪。” “怎么了笛丙?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵漾脂,是天一觀的道長。 經(jīng)常有香客問我胚鸯,道長骨稿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任姜钳,我火速辦了婚禮坦冠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘哥桥。我一直安慰自己辙浑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布拟糕。 她就那樣靜靜地躺著判呕,像睡著了一般倦踢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上佛玄,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天硼一,我揣著相機(jī)與錄音累澡,去河邊找鬼梦抢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛愧哟,可吹牛的內(nèi)容都是我干的奥吩。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼蕊梧,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼霞赫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肥矢,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤端衰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后甘改,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旅东,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年十艾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抵代。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡忘嫉,死狀恐怖荤牍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情庆冕,我是刑警寧澤康吵,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站访递,受9級(jí)特大地震影響晦嵌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜力九,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一耍铜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧跌前,春花似錦棕兼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽靶衍。三九已至,卻和暖如春茎芋,著一層夾襖步出監(jiān)牢的瞬間颅眶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來泰國打工田弥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涛酗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓偷厦,卻偏偏與公主長得像商叹,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子只泼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容