从first commit开始学Vue源码之发布订阅模式(一)

597 阅读1分钟

发布订阅模式是vue使用的最关键的一个模式之一,也是vue实现响应式的关键。因此我们先从发布订阅模式学起。

发布订阅模式概念:发布订阅模式,也可以叫做:让我康康模式,或者凑热闹模式,就是你关注的事情发生改变了,第一时间通知你,最常见的就是微博上的关注,知乎上的关注,有两种推送方式一种是被动推送,只有你主动去看了,才会把你关注的人最新消息发给你。还有一种是主动推送模式,一有新情况就推新消息给你,比如手机经常来的通知:你关注的新番更新啦!(没有巨人第五话看我快死了,莱纳你快坐啊!)

我来逝世:

0.1v简单版本,先定义两个类,发布者和订阅者

发布者:

export default class Publisher {
    constructor(name) {
        this.name = name
        this.sub = []
    }
/**
 * update方法
 */
    update() {
        console.log(`${this.name}我发布啦!`)
        for(let i = 0; i < this.sub.length; i++) {
            this.sub[i].afterUpdate()
        }
    }
/**
 *  
 */ 
    addSub(sub) {
        this.sub.push(sub)
        console.log(`${sub.name}已经订阅!`)
    }

    removeSub(sub) {
        let index = this.sub.findIndex(sub)
        this.sub.splice(index, 1)
    }
}

订阅者:

export default class Subscriber {
    constructor(name) {
        this.name = name
    }

    afterUpdate() {
        console.log(`${this.name}我爱看进击的巨人!`)
    }
}

接下来是测试代码:我们新建一个巨人发布者,然后我、小明、小红都订阅了它,当巨人更新时应该能触发我们的更新后执行函数。

import Publisher from './publisher.mjs'
import Subscriber from './subscriber.mjs'

const TITAN = new Publisher(`Attack on Titan`)
let me = new Subscriber(`bobo`)
let ming = new Subscriber(`xiaoming`)
let hong = new Subscriber(`xiaohong`)

TITAN.addSub(me)
TITAN.addSub(ming)
TITAN.addSub(hong)

TITAN.update()

测试结果:

这时看起来有些古怪,讲道理,订阅应该是订阅者去订阅,这么成发布者订阅了?而且我、小明、小红也许不想执行相同的方法,我想更新后立马就看,小明吃中饭时看,小红想晚上看,因此我们给订阅者添加订阅方法并给构造函数多加一个参数接受自定义函数。

订阅者改动如下:

export default class Subscriber {
    constructor(name, fn) {
        this.name = name
        this.fn = fn
    }

    afterUpdate() {
        this.fn()
    }

    subscribe(pub) {
        pub.addSub(this)
        console.log(`${pub.name}被我${this.name}订阅啦!`)
    }
}

测试代码改动如下:

import Publisher from './publisher.mjs'
import Subscriber from './subscriber.mjs'

const TITAN = new Publisher(`Attack on Titan`)
let me = new Subscriber(`bobo`, function(){console.log(`${this.name}要立刻看`)})
let ming = new Subscriber(`xiaoming`, function(){console.log(`${this.name}到中午看`)})
let hong = new Subscriber(`xiaohong`, function(){console.log(`${this.name}到晚上看`)})

me.subscribe(TITAN)
ming.subscribe(TITAN)
hong.subscribe(TITAN)

TITAN.update()

测试结果:

难道我就订阅一个《进击的巨人》?我还想要订阅《BeatStars》!我还想看史莱姆装杯!继续修改测试代码:

import Publisher from './publisher.mjs'
import Subscriber from './subscriber.mjs'

const TITAN = new Publisher(`Attack on Titan`)
const BEATSTRAS = new Publisher(`BEATSTARS`)
const SLIME = new Publisher(`Slime`)
let me = new Subscriber(`bobo`, function(){console.log(`${this.name}要立刻看`)})
let ming = new Subscriber(`xiaoming`, function(){console.log(`${this.name}到中午看`)})
let hong = new Subscriber(`xiaohong`, function(){console.log(`${this.name}到晚上看`)})

me.subscribe(TITAN)
ming.subscribe(TITAN)
hong.subscribe(TITAN)


me.subscribe(BEATSTRAS)
me.subscribe(SLIME)

TITAN.update()

测试结果:

这时候还有一个问题,我想要立刻看进击的巨人,中午看beatstars,晚上看史莱姆。可是他们一更新,我执行的都是立马看,看的我好累哦!因此我们要做好时间管理!

对每一个妹子,哦,错了,对订阅的每部动漫,都采取不同的观看方式,而不是一直的观看方式。继续修改订阅者和测试代码:

export default class Subscriber {
    constructor(name) {
        this.name = name
        this.subFnArr = new Map()
    }

    afterUpdate(pub) {
       let fn = this.subFnArr.get(pub).bind(this)
       fn()
    }

    subscribe(pub, fn) {
        pub.addSub(this)
        this.subFnArr.set(pub, fn)
        console.log(`${pub.name}被我${this.name}订阅啦!`)
    }
}
import Publisher from './publisher.mjs'
import Subscriber from './subscriber.mjs'

const TITAN = new Publisher(`Attack on Titan`)
const BEATSTRAS = new Publisher(`BEATSTARS`)
const SLIME = new Publisher(`Slime`)
let me = new Subscriber(`bobo`)
let ming = new Subscriber(`xiaoming`)
let hong = new Subscriber(`xiaohong`)

me.subscribe(TITAN, function(){console.log(`${this.name}要立刻看`)})
ming.subscribe(TITAN, function(){console.log(`${this.name}到中午看`)})
hong.subscribe(TITAN,  function(){console.log(`${this.name}到晚上看`)})


me.subscribe(BEATSTRAS, function(){console.log(`${this.name}到中午边吃饭边看`)})
me.subscribe(SLIME, function(){console.log(`${this.name}到晚上996下班回家看`)})

TITAN.update()
BEATSTRAS.update()
SLIME.update()

测试结果: