• <fieldset id="8imwq"><menu id="8imwq"></menu></fieldset>
  • <bdo id="8imwq"><input id="8imwq"></input></bdo>
    最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
    問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
    當(dāng)前位置: 首頁 - 科技 - 知識百科 - 正文

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

    來源:懂視網(wǎng) 責(zé)編:小采 時間:2020-11-27 22:20:39
    文檔

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

    淺析從vue源碼看觀察者模式:觀察者模式 首先話題下來,我們得反問一下自己,什么是觀察者模式? 概念 觀察者模式(Observer):通常又被稱作為發(fā)布-訂閱者模式。它定義了一種一對多的依賴關(guān)系,即當(dāng)一個對象的狀態(tài)發(fā)生改變的時候,所有依賴于它的對象都會得到通知并自動更新,解決了
    推薦度:
    導(dǎo)讀淺析從vue源碼看觀察者模式:觀察者模式 首先話題下來,我們得反問一下自己,什么是觀察者模式? 概念 觀察者模式(Observer):通常又被稱作為發(fā)布-訂閱者模式。它定義了一種一對多的依賴關(guān)系,即當(dāng)一個對象的狀態(tài)發(fā)生改變的時候,所有依賴于它的對象都會得到通知并自動更新,解決了

    觀察者模式

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

    概念

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

    講個故事

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

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

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

    適用性

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

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

    vue 對于觀察者模式的使用

    vue 使用到觀察者模式的地方有很多,這里我們主要談?wù)剬τ跀?shù)據(jù)初始化這一塊的。

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

    1、實現(xiàn)數(shù)據(jù)劫持

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

    這一小節(jié),我們只看如何通過觀察者模式對數(shù)據(jù)進行劫持。

    1.1、遞歸遍歷

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

    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、發(fā)布/訂閱

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

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

    return new Observer(value)

    2、消息封裝,實現(xiàn) "中轉(zhuǎn)站"

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

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

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

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

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

    2.1、Dep

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

    let uid = 0
    
    export default class Dep {
     static target: ?Watcher;
     id: number;
     subs: Array<Watcher>;
    
     constructor () {
     // 用來給每個訂閱者 Watcher 做唯一標(biāo)識符,防止重復(fù)收集
     this.id = uid++
     // 定義subs數(shù)組,用來做依賴收集(收集所有的訂閱者 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數(shù)組,用來收集訂閱者Watcher
    2. 當(dāng)劫持到數(shù)據(jù)變更的時候,通知訂閱者Watcher進行update操作

    源碼中,還拋出了兩個方法用來操作 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 意為觀察者,它負責(zé)做的事情就是訂閱 Dep ,當(dāng)Dep 發(fā)出消息傳遞(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 () {
     // 將目標(biāo)收集到目標(biāo)棧
     pushTarget(this)
     const vm = this.vm
     
     let value = this.getter.call(vm, vm)
     // 刪除目標(biāo)
     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)
     }
     }
     }
    
     // 訂閱者'消費'動作,當(dāng)接收到變更時則會執(zhí)行
     update () {
     this.run()
     }
    
     run () {
     const value = this.get()
     const oldValue = this.value
     this.value = value
     this.cb.call(this.vm, value, oldValue)
     }
    }
    
    

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

    現(xiàn)在再去看 Dep 和 Watcher,我們需要知道兩個點

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

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

    更多的應(yīng)用

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

    $emit 負責(zé)發(fā)布消息,并對訂閱者 $on 做統(tǒng)一消費,即執(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é)

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

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

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

    聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文檔

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

    淺析從vue源碼看觀察者模式:觀察者模式 首先話題下來,我們得反問一下自己,什么是觀察者模式? 概念 觀察者模式(Observer):通常又被稱作為發(fā)布-訂閱者模式。它定義了一種一對多的依賴關(guān)系,即當(dāng)一個對象的狀態(tài)發(fā)生改變的時候,所有依賴于它的對象都會得到通知并自動更新,解決了
    推薦度:
    標(biāo)簽: VUE 觀察者模式 vue源碼
    • 熱門焦點

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top
    主站蜘蛛池模板: 国产成人精品福利网站在线| 精品在线免费观看| 日本一区二区三区精品中文字幕| 日韩精品一区二区三区色欲AV| 亚洲精品高清一二区久久| 久久亚洲精品无码观看不卡| 日本精品一区二区三区四区| 亚洲av无码精品网站| 国产精品伊人久久伊人电影| 久久亚洲中文字幕精品一区四| 成人午夜精品网站在线观看| 亚洲精品美女久久777777| 国产精品三级国产电影| 囯产精品一品二区三区| 色综合久久精品中文字幕首页| 日韩精品区一区二区三VR| 国产精品福利一区二区久久| 国产精品亚洲w码日韩中文| 99re66热这里只有精品| 色欲国产麻豆一精品一AV一免费| 四虎影视永久在线精品免费| 国产午夜精品理论片| www.日韩精品| 亚洲一日韩欧美中文字幕欧美日韩在线精品一区二 | 国产午夜精品无码| 四虎国产精品永久地址99新强| 精品乱码一区二区三区四区| 中文字幕日韩精品在线| 欧美激情视频精品一区二区 | 久久99精品久久久久久水蜜桃| 无码精品视频一区二区三区| 亚洲国产精品无码久久青草| 久久国产精品无码网站| 国内精品视频在线观看| 精品国精品无码自拍自在线| 无码精品A∨在线观看中文| 亚洲国产成人久久精品99| 精品免费久久久久国产一区 | 精品乱子伦一区二区三区高清免费播放 | 99免费精品视频| 国产精品国产三级国产专播|