您好,登錄后才能下訂單哦!
觀察者模式
首先話題下來(lái),我們得反問(wèn)一下自己,什么是觀察者模式?
概念
觀察者模式(Observer):通常又被稱作為發(fā)布-訂閱者模式。它定義了一種一對(duì)多的依賴關(guān)系,即當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變的時(shí)候,所有依賴于它的對(duì)象都會(huì)得到通知并自動(dòng)更新,解決了主體對(duì)象與觀察者之間功能的耦合。
講個(gè)故事
上面對(duì)于觀察者模式的概念可能會(huì)比較官方化,所以我們講個(gè)故事來(lái)理解它。
A:是共產(chǎn)黨派往國(guó)民黨密探,代號(hào) 001(發(fā)布者)
B:是共產(chǎn)黨的通信人員,負(fù)責(zé)與 A 進(jìn)行秘密交接(訂閱者)
適用性
以下任一場(chǎng)景都可以使用觀察者模式
vue 對(duì)于觀察者模式的使用
vue 使用到觀察者模式的地方有很多,這里我們主要談?wù)剬?duì)于數(shù)據(jù)初始化這一塊的。
var vm = new Vue({ data () { return { a: 'hello vue' } } })
1、實(shí)現(xiàn)數(shù)據(jù)劫持
上圖我們可以看到,vue 是利用的是 Object.defineProperty() 對(duì)數(shù)據(jù)進(jìn)行劫持。 并在數(shù)據(jù)傳遞變更的時(shí)候封裝了一層中轉(zhuǎn)站,即我們看到的 Dep 和 Watcher 兩個(gè)類。
這一小節(jié),我們只看如何通過(guò)觀察者模式對(duì)數(shù)據(jù)進(jìn)行劫持。
1.1、遞歸遍歷
我們都知道,vue 對(duì)于 data 里面的數(shù)據(jù)都做了劫持的,那只能對(duì)對(duì)象進(jìn)行遍歷從而完成每個(gè)屬性的劫持,源碼具體如下
walk (obj: Object) { const keys = Object.keys(obj) // 遍歷將其變成 vue 的訪問(wèn)器屬性 for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } }
1.2、發(fā)布/訂閱
從上面對(duì)象的遍歷我們看到了 defineReactive ,那么劫持最關(guān)鍵的點(diǎn)也在于這個(gè)函數(shù),該函數(shù)里面封裝了 getter 和 setter 函數(shù),使用觀察者模式,互相監(jiān)聽(tīng)
// 設(shè)置為訪問(wèn)器屬性,并在其 getter 和 setter 函數(shù)中,使用發(fā)布/訂閱模式,互相監(jiān)聽(tīng)。 export function defineReactive ( obj: Object, key: string, val: any ) { // 這里用到了觀察者(發(fā)布/訂閱)模式進(jìn)行了劫持封裝,它定義了一種一對(duì)多的關(guān)系,讓多個(gè)觀察者監(jiān)聽(tīng)一個(gè)主題對(duì)象,這個(gè)主題對(duì)象的狀態(tài)發(fā)生改變時(shí)會(huì)通知所有觀察者對(duì)象,觀察者對(duì)象就可以更新自己的狀態(tài)。 // 實(shí)例化一個(gè)主題對(duì)象,對(duì)象中有空的觀察者列表 const dep = new Dep() // 獲取屬性描述符對(duì)象(更多的為了 computed 里面的自定義 get 和 set 進(jìn)行的設(shè)計(jì)) const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } const getter = property && property.get const setter = property && property.set let childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, // 收集依賴,建立一對(duì)多的的關(guān)系,讓多個(gè)觀察者監(jiān)聽(tīng)當(dāng)前主題對(duì)象 get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() // 這里是對(duì)數(shù)組進(jìn)行劫持 if (Array.isArray(value)) { dependArray(value) } } } return value }, // 劫持到數(shù)據(jù)變更,并發(fā)布消息進(jìn)行通知 set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val if (newVal === value || (newVal !== newVal && value !== value)) { return } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = observe(newVal) dep.notify() } }) }
1.3、返回 Observer 實(shí)例
上面我們看到了observe 函數(shù),核心就是返回一個(gè) Observer 實(shí)例
return new Observer(value)
2、消息封裝,實(shí)現(xiàn) "中轉(zhuǎn)站"
首先我們要理解,為什么要做一層消息傳遞的封裝?
我們?cè)谥v解觀察者模式的時(shí)候有提到它的 適用性 。這里也同理,我們?cè)诮俪值綌?shù)據(jù)變更的時(shí)候,并進(jìn)行數(shù)據(jù)變更通知的時(shí)候,如果不做一個(gè)"中轉(zhuǎn)站"的話,我們根本不知道到底誰(shuí)訂閱了消息,具體有多少對(duì)象訂閱了消息。
這就好比上文中我提到的故事中的密探 A(發(fā)布者) 和共產(chǎn)黨 B(訂閱者)。密探 A 與 共產(chǎn)黨 B 進(jìn)行信息傳遞,兩人都知道對(duì)方這么一個(gè)人的存在,但密探 A 不知道具體 B 是誰(shuí)以及到底有多少共產(chǎn)黨(訂閱者)訂閱著自己,可能很多共產(chǎn)黨都訂閱著密探 A 的信息,so 密探 A(發(fā)布者) 需要通過(guò)暗號(hào) 收集到所有訂閱著其消息的共產(chǎn)黨們(訂閱者),這里對(duì)于訂閱者的收集其實(shí)就是一層封裝。然后密探 A 只需將消息發(fā)布出去,而訂閱者們接受到通知,只管進(jìn)行自己的 update 操作即可。
簡(jiǎn)單一點(diǎn),即收集完訂閱者們的密探 A 只管發(fā)布消息,共產(chǎn)黨 B 以及更多的共產(chǎn)黨只管訂閱消息并進(jìn)行對(duì)應(yīng)的 update 操作,每個(gè)模塊確保其獨(dú)立性,實(shí)現(xiàn)高內(nèi)聚低耦合這兩大原則。
廢話不多說(shuō),我們接下來(lái)直接開(kāi)始講 vue 是如何做的消息封裝的
2.1、Dep
Dep,全名 Dependency,從名字我們也能大概看出 Dep 類是用來(lái)做依賴收集的,具體怎么收集呢。我們直接看源碼
let uid = 0 export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () { // 用來(lái)給每個(gè)訂閱者 Watcher 做唯一標(biāo)識(shí)符,防止重復(fù)收集 this.id = uid++ // 定義subs數(shù)組,用來(lái)做依賴收集(收集所有的訂閱者 Watcher) this.subs = [] } // 收集訂閱者 addSub (sub: Watcher) { this.subs.push(sub) } depend () { if (Dep.target) { Dep.target.addDep(this) } } notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } // the current target watcher being evaluated. // this is globally unique because there could be only one // watcher being evaluated at any time. Dep.target = null
代碼很簡(jiǎn)短,但它做的事情卻很重要
源碼中,還拋出了兩個(gè)方法用來(lái)操作 Dep.target ,具體如下
// 定義收集目標(biāo)棧 const targetStack = [] export function pushTarget (_target: Watcher) { if (Dep.target) targetStack.push(Dep.target) // 改變目標(biāo)指向 Dep.target = _target } export function popTarget () { // 刪除當(dāng)前目標(biāo),重算指向 Dep.target = targetStack.pop() }
2.2、 Watcher
Watcher 意為觀察者,它負(fù)責(zé)做的事情就是訂閱 Dep ,當(dāng)Dep 發(fā)出消息傳遞(notify)的時(shí)候,所以訂閱著 Dep 的 Watchers 會(huì)進(jìn)行自己的 update 操作。廢話不多說(shuō),直接看源碼就知道了。
export default class Watcher { vm: Component; expression: string; cb: Function; constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: Object ) { this.vm = vm vm._watchers.push(this) this.cb = cb // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { // 解析表達(dá)式 this.getter = parsePath(expOrFn) if (!this.getter) { this.getter = function () {} } } this.value = this.get() } get () { // 將目標(biāo)收集到目標(biāo)棧 pushTarget(this) const vm = this.vm let value = this.getter.call(vm, vm) // 刪除目標(biāo) popTarget() return value } // 訂閱 Dep,同時(shí)讓 Dep 知道自己訂閱著它 addDep (dep: Dep) { const id = dep.id if (!this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (!this.depIds.has(id)) { // 收集訂閱者 dep.addSub(this) } } } // 訂閱者'消費(fèi)'動(dòng)作,當(dāng)接收到變更時(shí)則會(huì)執(zhí)行 update () { this.run() } run () { const value = this.get() const oldValue = this.value this.value = value this.cb.call(this.vm, value, oldValue) } }
上述代碼中,我刪除了一些與目前探討無(wú)關(guān)的代碼,如果需要進(jìn)行詳細(xì)研究的,可以自行查閱 vue2.5.3 版本的源碼。
現(xiàn)在再去看 Dep 和 Watcher,我們需要知道兩個(gè)點(diǎn)
兩者看似相互依賴,實(shí)則卻保證了其獨(dú)立性,保證了模塊的單一性。
更多的應(yīng)用
vue 還有一些地方用到了"萬(wàn)能"的觀察者模式,比如我們熟知的組件之間的事件傳遞,$on 以及 $emit 的設(shè)計(jì)。
$emit 負(fù)責(zé)發(fā)布消息,并對(duì)訂閱者 $on 做統(tǒng)一消費(fèi),即執(zhí)行 cbs 里面所有的事件。
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component { const vm: Component = this if (Array.isArray(event)) { for (let i = 0, l = event.length; i < l; i++) { this.$on(event[i], fn) } } else { (vm._events[event] || (vm._events[event] = [])).push(fn) } return vm } Vue.prototype.$emit = function (event: string): Component { const vm: Component = this let cbs = vm._events[event] if (cbs) { cbs = cbs.length > 1 ? toArray(cbs) : cbs const args = toArray(arguments, 1) for (let i = 0, l = cbs.length; i < l; i++) { cbs[i].apply(vm, args) } } return vm }
總結(jié)
本文探討了觀察者模式的基本概念、適用場(chǎng)景,以及在 vue 源碼中的具體應(yīng)用。這一節(jié)將總結(jié)一下觀察者模式的一些優(yōu)缺點(diǎn)
OK,本文到這就差不多了,更多的源碼設(shè)計(jì)思路細(xì)節(jié)將在同系列的其它文章中進(jìn)行一一解讀。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。