设计模式-发布订阅模式


设计模式-发布订阅模式

emit:发布

on:订阅 先订阅再发布,改变顺序使用

埋点?

过度使用?

什么是发布订阅模式

家有喜事,当然要大家一起庆祝,顺便收份子钱,于是我打开了通讯录,向亲朋好友发布这个好消息。讲完了吗? 是的,哈哈哈,这就是发布订阅模式,我发布一个消息,订阅这个消息的人收到了通知。

写一个简单的发布订阅事件

js

yourMessage = {};
yourMessage.messageList = [];
yourMessage.on = function (fn) {
    yourMessage.messageList.push(fn);
}
yourMessage.emit = function () {
    for (var i = 0, fn; fn = yourMessage.messageList[i++];) {
        fn.apply(this, arguments); //使用call 和 使用 bind 不一样
    }
}
yourMessage.on(function (name) {
    // console.log(Object.prototype.toString.call(name1))
    console.log(`欢迎, ${name}`)
})
yourMessage.on(function () {
    console.log('哈哈哈')
})
yourMessage.emit('花大姐', '马达话')
yourMessage.emit('勤大妈')

输出结果:

img

额外插播一点: 上面的代码里面说到,使用 call 和 使用 apply是不一样的,上面的运行结果是使用apply的运行结果,我们看一下使用call的运行结果:

img

对于结果不一样,我说下自己的见解,applycall 接收参数的形式不一样,apply以数组的形式接收参数,call接收的是一系列参数,arguments是类数组类型,用call去接收会把整个类数组传过去,用apply接收传给functionfunction接收的时候可以以解构的方式去赋值给形参。

这个栗子中,只要订阅就会把所有消息发布一次,但是有时候订阅者并不想听到你所有的消息,这时候就需要改进一下。

改进一下上面的栗子

js

yourMessage = {};
yourMessage.messageList = {};
yourMessage.on = function (key, fn) {
    if (!this.messageList[key]) this.messageList[key] = [];
    this.messageList[key].push(fn);
}
yourMessage.emit = function () {
    let key = Array.prototype.shift.call(arguments);
    if (!this.messageList[key] || this.messageList[key].length <= 0) return;
    for (var i = 0, fn; fn = this.messageList[key][i++];) {
        fn.apply(this, arguments);
    }
}
yourMessage.on('欢迎', function (name) {
    console.log(`欢迎, ${name}`)
})
yourMessage.on('哈哈', function (name) {
    console.log(`哈哈,${name}`)
})
yourMessage.emit('欢迎', '马达话')
yourMessage.emit('哈哈', '勤大妈')

img

再改进一下栗子

新增取消订阅和一次订阅

js

let eventEmitter = {
    list: {},
    //订阅
    on(key, fn) {
        if (!this.list[key]) {
            this.list[key] = [];
        }
        this.list[key].push(fn);
    },
    //发布
    emit() {
        let key = Array.prototype.shift.call(arguments);
        if (!this.list[key] || this.list[key].length <= 0) return;
        for (let i = 0, fn; fn = this.list[key][i++];) {
            fn.apply(this, arguments);
        }

    },
    //一次订阅
    once(key, fn) {
        let on = function () { //一次性订阅只需要把传入的订阅事件包装一下
            fn.apply(this, arguments);
            this.off(key, on);
        }
        this.on(key, on);
    },
    //取消订阅
    off(key, obj) {
        if (!this.list[key] || this.list[key].length <= 0) return;
        for (let i = 0, fn; fn = this.list[key][i]; i++) {
            if (fn === obj) {
                this.list[key].splice(i, 1);
                break;
            }
        }
    }
}

function fn1(content) {
    console.log('One ', content);
}

function fn2(content) {
    console.log('Two ', content)
}

eventEmitter.once('listen', fn1);
eventEmitter.once('listen', fn2);

eventEmitter.emit('listen', '哈哈哈')

//eventEmitter.off('listen', fn1);
eventEmitter.emit('listen', '哈哈哈')

关于发布订阅事件的顺序

前面实现的发布订阅都需要先订阅再发布,才可以收到消息,这次我们来实现一个可以支持先发布再订阅的发布订阅,也就是可以支持历史信息的订阅。

js

//改变发布订阅顺序
let eventEmitter = {
  list: {},
  cached: {},
  //订阅
  on(key, fn) {
    if (!this.list[key]) {
      this.list[key] = [];
    }
    if (this.list[key].indexOf(fn) != -1) return; //防止多次订阅
    this.list[key].push(fn);
    if (this.cached[key])
      for (let i = 0, content; content = this.cached[key][i]; i++) {
        fn.apply(this, content);
      }
  },
  //发布
  emit() {
    let key = Array.prototype.shift.call(arguments);
    if (!this.cached[key]) this.cached[key] = [];
    this.cached[key].push(arguments);
    if (!this.list[key] || this.list[key].length <= 0) return;
    for (let i = 0, fn; fn = this.list[key][i++];) {
      fn.apply(this, arguments);
    }

  },
  //一次订阅
  once(key, fn) {
    let on = function () {
      fn.apply(this, arguments);
      this.off(key, on);
    }
    this.on(key, on);
  },
  //取消订阅
  off(key, obj) {
    if (!this.list[key] || this.list[key].length <= 0) return;
    for (let i = 0, fn; fn = this.list[key][i]; i++) {
      if (fn === obj) {
        this.list[key].splice(i, 1);
        break;
      }
    }
  }
}

function fn1(content) {
  console.log('One ', content);
}

function fn2(content) {
  console.log('Two ', content)
}
eventEmitter.emit('listen', '哈哈哈')
eventEmitter.on('listen', fn1);
eventEmitter.on('listen', fn2);



//eventEmitter.off('listen', fn1);
//eventEmitter.emit('listen', '哈哈哈')

运行结果:

img

其实还有优化的空间。。。。。。


文章作者: 木叶勇
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 木叶勇 !
 上一篇
JavaScript-Promise JavaScript-Promise
Promisepromise.resove()–> 产生一个promise对象 resolve()、reject()并不停止Promise,可使用return resolve(...) 或 return reject(...) 建议不
2020-06-16
下一篇 
JavaScript-宏任务、微任务 和 EventLoop JavaScript-宏任务、微任务 和 EventLoop
宏任务、微任务 和 EventLoop让我们来讲一个小故事 6月份在火辣辣的长沙,走在热浪滔天的五一广场,口干舌燥,想来一杯冰凉凉的奶茶,但买奶茶的人特别多,作为一个文明人,我当然选择排队。前面有一个火辣辣的美眉,她点了一杯波霸珍珠奶茶,然
2020-06-16
  目录