国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

淺析從vue源碼看觀察者模式

2019-11-19 14:27:39
字體:
來源:轉載
供稿:網友

觀察者模式

首先話題下來,我們得反問一下自己,什么是觀察者模式?

概念

觀察者模式(Observer):通常又被稱作為發布-訂閱者模式。它定義了一種一對多的依賴關系,即當一個對象的狀態發生改變的時候,所有依賴于它的對象都會得到通知并自動更新,解決了主體對象與觀察者之間功能的耦合。

講個故事

上面對于觀察者模式的概念可能會比較官方化,所以我們講個故事來理解它。

A:是共產黨派往國民黨密探,代號 001(發布者)
B:是共產黨的通信人員,負責與 A 進行秘密交接(訂閱者)

  1. A 日常工作就是在明面采集國民黨的一些情報
  2. B 則負責暗中觀察著 A
  3. 一旦 A 傳遞出一些有關國民黨的消息(更多時候需要對消息進行封裝傳遞,后面根據源碼具體分析)
  4. B 會立馬訂閱到該消息,然后做一些相對應的變更,比如說通知共產黨們做一些事情應對國民黨的一些動作。

適用性

以下任一場景都可以使用觀察者模式

  1. 當一個抽象模型有兩個方面,其中一個方面依賴于另一方面。講這兩者封裝在獨立的對象中可以讓它們可以各自獨立的改變和復用
  2. 當一個對象的改變的時候,需要同時改變其它對象,但是卻不知道具體多少對象有待改變
  3. 當一個對象必須通知其它對象,但是卻不知道具體對象到底是誰。換句話說,你不希望這些對象是緊密耦合的。

vue 對于觀察者模式的使用

vue 使用到觀察者模式的地方有很多,這里我們主要談談對于數據初始化這一塊的。

var vm = new Vue({ data () {  return {   a: 'hello vue'  } }})

1、實現數據劫持

上圖我們可以看到,vue 是利用的是 Object.defineProperty() 對數據進行劫持。 并在數據傳遞變更的時候封裝了一層中轉站,即我們看到的 Dep 和 Watcher 兩個類。

這一小節,我們只看如何通過觀察者模式對數據進行劫持。

1.1、遞歸遍歷

我們都知道,vue 對于 data 里面的數據都做了劫持的,那只能對對象進行遍歷從而完成每個屬性的劫持,源碼具體如下

walk (obj: Object) { const keys = Object.keys(obj) // 遍歷將其變成 vue 的訪問器屬性 for (let i = 0; i < keys.length; i++) {  defineReactive(obj, keys[i], obj[keys[i]]) }}

1.2、發布/訂閱

從上面對象的遍歷我們看到了 defineReactive ,那么劫持最關鍵的點也在于這個函數,該函數里面封裝了 getter  和 setter 函數,使用觀察者模式,互相監聽

// 設置為訪問器屬性,并在其 getter 和 setter 函數中,使用發布/訂閱模式,互相監聽。export function defineReactive ( obj: Object, key: string, val: any) { // 這里用到了觀察者(發布/訂閱)模式進行了劫持封裝,它定義了一種一對多的關系,讓多個觀察者監聽一個主題對象,這個主題對象的狀態發生改變時會通知所有觀察者對象,觀察者對象就可以更新自己的狀態。 // 實例化一個主題對象,對象中有空的觀察者列表 const dep = new Dep()  // 獲取屬性描述符對象(更多的為了 computed 里面的自定義 get 和 set 進行的設計) 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,  // 收集依賴,建立一對多的的關系,讓多個觀察者監聽當前主題對象  get: function reactiveGetter () {   const value = getter ? getter.call(obj) : val   if (Dep.target) {    dep.depend()    if (childOb) {     childOb.dep.depend()     // 這里是對數組進行劫持     if (Array.isArray(value)) {      dependArray(value)     }    }   }   return value  },  // 劫持到數據變更,并發布消息進行通知  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 實例

上面我們看到了observe 函數,核心就是返回一個 Observer 實例

return new Observer(value)

2、消息封裝,實現 "中轉站"

首先我們要理解,為什么要做一層消息傳遞的封裝?

我們在講解觀察者模式的時候有提到它的 適用性 。這里也同理,我們在劫持到數據變更的時候,并進行數據變更通知的時候,如果不做一個"中轉站"的話,我們根本不知道到底誰訂閱了消息,具體有多少對象訂閱了消息。

這就好比上文中我提到的故事中的密探 A(發布者) 和共產黨 B(訂閱者)。密探 A 與 共產黨 B 進行信息傳遞,兩人都知道對方這么一個人的存在,但密探 A 不知道具體 B 是誰以及到底有多少共產黨(訂閱者)訂閱著自己,可能很多共產黨都訂閱著密探 A 的信息,so 密探 A(發布者) 需要通過暗號 收集到所有訂閱著其消息的共產黨們(訂閱者),這里對于訂閱者的收集其實就是一層封裝。然后密探 A 只需將消息發布出去,而訂閱者們接受到通知,只管進行自己的 update 操作即可。

簡單一點,即收集完訂閱者們的密探 A 只管發布消息,共產黨 B 以及更多的共產黨只管訂閱消息并進行對應的 update 操作,每個模塊確保其獨立性,實現高內聚低耦合這兩大原則。

廢話不多說,我們接下來直接開始講 vue 是如何做的消息封裝的

2.1、Dep

Dep,全名 Dependency,從名字我們也能大概看出 Dep 類是用來做依賴收集的,具體怎么收集呢。我們直接看源碼

let uid = 0export default class Dep { static target: ?Watcher; id: number; subs: Array<Watcher>; constructor () {  // 用來給每個訂閱者 Watcher 做唯一標識符,防止重復收集  this.id = uid++  // 定義subs數組,用來做依賴收集(收集所有的訂閱者 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

代碼很簡短,但它做的事情卻很重要

  1. 定義subs數組,用來收集訂閱者Watcher
  2. 當劫持到數據變更的時候,通知訂閱者Watcher進行update操作

源碼中,還拋出了兩個方法用來操作 Dep.target ,具體如下

// 定義收集目標棧const targetStack = []export function pushTarget (_target: Watcher) { if (Dep.target) targetStack.push(Dep.target) // 改變目標指向 Dep.target = _target}export function popTarget () { // 刪除當前目標,重算指向 Dep.target = targetStack.pop()}

2.2、 Watcher

Watcher 意為觀察者,它負責做的事情就是訂閱 Dep ,當Dep 發出消息傳遞(notify)的時候,所以訂閱著 Dep 的 Watchers 會進行自己的 update 操作。廢話不多說,直接看源碼就知道了。

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 {   // 解析表達式   this.getter = parsePath(expOrFn)   if (!this.getter) {    this.getter = function () {}   }  }  this.value = this.get() } get () {  // 將目標收集到目標棧  pushTarget(this)  const vm = this.vm    let value = this.getter.call(vm, vm)  // 刪除目標  popTarget()    return value } // 訂閱 Dep,同時讓 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)   }  } } // 訂閱者'消費'動作,當接收到變更時則會執行 update () {  this.run() } run () {  const value = this.get()  const oldValue = this.value  this.value = value  this.cb.call(this.vm, value, oldValue) }}

上述代碼中,我刪除了一些與目前探討無關的代碼,如果需要進行詳細研究的,可以自行查閱 vue2.5.3 版本的源碼。

現在再去看 Dep 和 Watcher,我們需要知道兩個點

  1. Dep 負責收集所有的訂閱者 Watcher ,具體誰不用管,具體有多少也不用管,只需要通過 target 指向的計算去收集訂閱其消息的 Watcher 即可,然后只需要做好消息發布 notify 即可。
  2. Watcher 負責訂閱 Dep ,并在訂閱的時候讓 Dep 進行收集,接收到 Dep 發布的消息時,做好其 update 操作即可。

兩者看似相互依賴,實則卻保證了其獨立性,保證了模塊的單一性。

更多的應用

vue 還有一些地方用到了"萬能"的觀察者模式,比如我們熟知的組件之間的事件傳遞,$on 以及 $emit 的設計。

$emit 負責發布消息,并對訂閱者 $on 做統一消費,即執行 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}

總結

本文探討了觀察者模式的基本概念、適用場景,以及在 vue 源碼中的具體應用。這一節將總結一下觀察者模式的一些優缺點

  1. 目標和觀察者間的抽象耦合:一個目標只知道他有一系列的觀察者(目標進行依賴收集),卻不知道其中任意一個觀察者屬于哪一個具體的類,這樣目標與觀察者之間的耦合是抽象的和最小的。
  2. 支持廣播通信:觀察者里面的通信,不像其它通常的一些請求需要指定它的接受者。通知將會自動廣播給所有已訂閱該目標對象的相關對象,即上文中的 dep.notify() 。當然,目標對象并不關心到底有多少對象對自己感興趣,它唯一的職責就是通知它的各位觀察者,處理還是忽略一個通知取決于觀察者本身。
  3. 一些意外的更新:因為一個觀察者它自己并不知道其它觀察者的存在,它可能對改變目標的最終代價一無所知。如果觀察者直接在目標上做操作的話,可能會引起一系列對觀察者以及依賴于這些觀察者的那些對象的更新,所以一般我們會把一些操作放在目標內部,防止出現上述的問題。

OK,本文到這就差不多了,更多的源碼設計思路細節將在同系列的其它文章中進行一一解讀。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 东乡族自治县| 宁蒗| 平果县| 安多县| 申扎县| 铜梁县| 房山区| 肇州县| 旬阳县| 昂仁县| 林甸县| 察哈| 丰顺县| 开封县| 玉龙| 明水县| 普格县| 阳原县| 清新县| 滦平县| 义乌市| 淮阳县| 凤庆县| 太仆寺旗| 米易县| 沅陵县| 禄丰县| 中方县| 南和县| 临洮县| 观塘区| 澄江县| 平安县| 凤凰县| 安仁县| 清水县| 温州市| 芜湖市| 抚顺县| 兴化市| 南丰县|