-
Vue 发布/订阅 与 观察者模式
2020-12-29 22:58:43现在网上很多文章都讲不清楚什么是发布/订阅和观察者模式,要不就是复制粘贴过来的,百度都能查处一大推一模一样的...那么这个房子的主人也就是房东是一个发布者(publish),你或者其他需要买房的人就是订阅者(subs现在网上很多文章都讲不清楚什么是发布/订阅和观察者模式,要不就是复制粘贴过来的,百度都能查处一大推一模一样的文章。很气愤!!!
那么我将根据自己的理解去解释发布/订阅和观察者模式,如果有大佬觉得不对,欢迎在评论区留下宝贵意见
发布/订阅模式
发布/订阅模包含:
- 发布者
- 订阅者
- 信号中心
假设你要买房子,但是你有没有房源信息,那么你会找到谁?你肯定会去问房屋中介,那么这个房屋中介就是一个信号中心。那么这个房子的主人也就是房东是一个发布者(publish),你或者其他需要买房的人就是订阅者(subscribe)。
那么在Vue是如何实现的呢?
// Vue 中使用方法 let vm = new Vue() // 订阅者 vm.$on('change', () => { console.log('change') }) // 订阅者 vm.$on('change', () => { console.log('change1') }) // 发布者 vm.$emit('change')
以上就是 Vue 中使用的方法,通过$on方法来订阅change事件,通过$emit方法来发布change事件,那么订阅者获取到事件之后就会执行相对应的方法。这就是发布/订阅模式(publish-subscribe pattern )。接下来我们老模拟实现一个发布/订阅模式来加深印象
首选我们来分析下订阅/发布模式是如何实现的
- 定义一个类Vue(也可以是其他名称)
- 类里面有两个方法$on(订阅者)和$emit(发布者)
- $on方法里接受两个参数,一个是事件类型,另一个是执行的方法
- $emit方法里接受一个参数,事件类型
接下来根据我们分析的情况通过代码来模拟实现如下:
// 事件中心 class Vue { constructor () { // 创建一个空对象,结构是{ event: [function1, function2, ...] } this.subs = Object.create(null) } // 定义$on方法(订阅者)event: 事件名称 handler: 需要执行的方法 $on (event, handler) { // 匹配对应的事件,如果事件存在就取事件对应的值,否则就取空数组 this.subs[event] = this.subs[event] || [] // 找到对应的事件将值(函数)push到对应的事件值中 this.subs[event].push(handler) } // 定义$emit方法(发布者) $emit(event) { // 判断当前事件是否存在,存在执行对应值的方法 if(this.subs[event]) { this.subs[event].forEach(handler => { handler() }) } } } // 测试 let vm = new Vue() vm.$on('change', () => { console.log('change') }) vm.$emit('change')
浏览器执行以上代码会发现控制台打印出'change',以上就是发布/订阅模式的实现方式
关注我后续我会继续更新观察者模式
-
vue中发布订阅者模式
2020-01-13 10:54:07 -
手写实现vue 发布 订阅模式和观察者模式
2020-12-09 20:40:27发布 订阅模式 // 事件触发器 class EventEmitter { constructor () { // { 'click': [fn1, fn2], 'change': [fn] } this.subs = Object.create(null) } // 注册事件 $on (eventType, handler) { ...发布 订阅模式
// 事件触发器 class EventEmitter { constructor () { // { 'click': [fn1, fn2], 'change': [fn] } this.subs = Object.create(null) } // 注册事件 $on (eventType, handler) { debugger this.subs[eventType] = this.subs[eventType] || [] this.subs[eventType].push(handler) debugger } // 触发事件 $emit (eventType) { debugger if (this.subs[eventType]) { debugger this.subs[eventType].forEach(handler => { handler() debugger }) } } } // 测试 let em = new EventEmitter() em.$on('click', () => { console.log('click1') }) em.$on('click', () => { console.log('click2') }) em.$on('fn', ()=> { console.log('fn') }) em.$emit('click') em.$emit('fn')
观察者模式
// 发布者-目标 class Dep { constructor () { // 记录所有的订阅者 this.subs = [] } // 添加订阅者 addSub (sub) { if (sub && sub.update) { this.subs.push(sub) } } // 发布通知 notify () { this.subs.forEach(sub => { sub.update() }) } } // 订阅者-观察者 class Watcher { update () { console.log('update') } } // 测试 let dep = new Dep() let watcher1 = new Watcher() let watcher2 = new Watcher() dep.addSub(watcher1) dep.addSub(watcher2) dep.notify()
两者的区别
-
vue发布订阅者模式$emit、$on
2019-07-21 11:19:10在vue中自定义事件,就是用了发布订阅者模式,vm.$on通过自定义事件,事先订阅一件事,vm.$emit通过自定义事件发布消息,vm.$on会接收到。非父子组件数据传递,通常会用到这两个方法: 下边我们先来用js实现发布...家住南京的李先生,最近看上了一套房子,到了售楼处才发现,该楼盘放在早已售罄。好在售楼处工作人员王女士出于好心告知李先生,不久之后还会有一些尾盘推出,开发商在办理相关手续,手续办理好之后便可购买。但到底什么时候能办理好,目前无人知道。于是李先生记下了售楼处电话,每天都会打电话询问是否到了购买时间。除了李先生,还有它的朋友、七大姑八大姨也会每天定点咨询这个问题。一周之后,售楼处王女士决定辞职,原因厌倦了每天回答1000个相同内容的电话。。。
当然,现实中没有那么愚蠢的销售公司,实际上故事是这样的:李先生把他的电话号码留给售楼处,售楼处工作人员答应他,新楼盘一推出会马上通知李先生,他的朋友及七大姑也把电话号码留在了售楼处,新楼盘一推出工作人员会翻开电话簿,依次遍历上边的电话号码,发送信息告知他们。。。
在上边案例中,发送短信依次告知 就是一个典型的发布、订阅者模式;李先生和他的朋友及七大姑等购买的人都是订阅者,他们订阅了楼盘开售的消息。售楼处作为发布者,会在适合的时候遍历电话簿上的电话号码,依次给购房者发布消息。
使用发布订阅者模式有着显著的优点:购房者不需要天天询问开售时间,,在适合的时间点,售楼处会通知这些消息的订阅者;购房者和售楼处之间不再强耦合在一起,只要有新的购房者出现时,只需要把电话号码留在售楼处即可;而售楼处不会关心购房者任何情况,不管购房者 是男 是女 是猪?还是外星人(黑人问号脸)。售楼处的任何变动也不会影响购买者(当然除卷钱跑路的)!只要售楼处记得发短信这件事即可。。。
在vue中自定义事件,就是用了发布订阅者模式,vm.$on通过自定义事件,事先订阅一件事,vm.$emit通过自定义事件发布消息,vm.$on会接收到。非父子组件数据传递,通常会用到这两个方法:
下边我们先来用js实现发布订阅者模式:(这种模式必须先订阅才能接收到信息,注意顺序!)
function Center(){ //订阅(售楼)中心,把所有要买楼房的人统一放在一个对象里,形成一对多关系 // {"eventName":[callback1,callback2,··· ···]} // 属性eventName:是电话薄 // 属性值是一个数组,里边每一个函数就是订阅楼盘信息的人(购房者) this.subscriptionCenter = {}; } //on和emit都是公共方法,所以写在原型上了 Center.prototype.on = function ( eventName , callback ){ if( this.subscriptionCenter[eventName] ){ this.subscriptionCenter[eventName].push(callback); }else{ this.subscriptionCenter[eventName] = [callback]; } } Center.prototype.emit = function ( eventName , ...publishInfo ){ if( this.subscriptionCenter[eventName] ){ this.subscriptionCenter[eventName].forEach( cb=>cb( ...publishInfo ) ) } } var center = new Center(); //subscription是电话薄,后边函数是李先生订阅的信息,等待售楼中心发出日期 center.on("subscription",function MrLi(time){ console.log(`您好李先生,新楼盘于${time}开售`)//因为订阅了售楼中心的信息,所以能接收到售楼中心传过来的消息 }) //后边函数是李先生的朋友订阅的信息 center.on("subscription",function friend(time){ console.log(`您好李先生的朋友,新楼盘于${time}开售`) }) //后边函数是七大姑订阅的信息 center.on("subscription",function aunt(time){ console.log(`您好七大姑,新楼盘于${time}开售`) }) // 订阅(售楼)中心通过电话薄,给每一个订阅者(购房者)发送消息 center.emit("subscription","2019年7月21日")
执行结果:
建议大家,把代码赋值到编辑器里边,方便查看理解。。。(有不懂得地方欢迎留言询问)
再来看下vue中$emit用法:
vm.$emit( "eventName" , [ ... params ] )
触发当前组件的自定义事件,参数一:自定义事件名,参数二:发送的数据,可以是一个字符串或一个数组,其实在vue源码中,即使你传的是一个字符串参数,还是会转化为数组,只不过在最后自定义事件执行的时候,会使用handler.aplly( context, args)形式把参数传入自定义的方法。(最后我会分析下vm.$emit和vm.$on的源码)
eg:先看下$emit的用法,在created中使用$on事先监听一个自定义方法,当按钮被点击,$emit这个方法会执行,事件名对应的自定义事件会被触发,也就是$on监听一个自定义方法被触发。
<template> <div> <button @click="handleEmit">click</button> </div> </template> <script> export default { created() { // this.$on()可为监听的事件,绑定多个处理函数 this.$on('defaultEvent', this.emitFn1) }, methods: { emitFn1(value) { console.log(value)// '1' }, handleEmit() { this.$emit('defaultEvent', '1') } } }; </script>
$on用法:事先监听实例上边的自定义事件(也就是监听上边defaultEvent事件),事件由$emit触发,参数二:处理方法,它会接收emit触发的自定义事件的所有参数。
$emit("eventName",[...params]) $on("event",function(...params){ })
这里就要不举例了,大家可以自己写一写。。。
源码分析:
vm.$on
源码:
$on方法 是被定义在Vue的原型上,此方法接收2个参数——事件名、事件名对应的处理方法,开始会判断下事件名是否是数组,是数组则循环里边的事件名,依次转化为单个字符串形式,否说明是一个字符串,接下来看下实例中的_events[event]是否有值,有的话,会把处理方法push到数组中;没有值则把值设置为一个数组,再把处理方法push到数组中。
从上边源码中可分析出:
(1)vm.$on可绑定多个事件名,前提需要以数组类型定义,多个事件名可对应同一个处理方法。
(2)实例上会有一个_events对象,对象key为$on绑定的事件名,值为一个数组,数组里边装的是处理方法。
vm.$emit()
源码:
$emit方法 定义在Vue的原型上,在源码中只传了一个参数,事件名,为什么呢?先往下看,开始如果是开发环境,把事件名转小写,如果转小写的事件名与传进来的事件名不等 且 实例中的_events对象中有事件名对应的处理方法,会报警告,不会有结果输出,这种情况是定义在$on和$emit中的事件名不一样 或者 子组件触发一个自定义事件名与父组件绑定的自定义事件名不一致,会抛出以上警告。
继续往下走,根据传进来事件名获取到实例的_events对象中的处理方法,如果有处理方法,掉用toArray方法,把$emit第一个参数(事件名)去掉,只留下参数二(传递的参数)args,并且会把参数二转化为一个数组,toArray方法最后我会介绍,接下来循环cbs数组执行 invokeWithErrorHandling(cbs[i], vm, args, vm, info) 方法,cbs[i]:事件处理方法; ags:传递的参数。
invokeWithErrorHandling方法:
invokeWithErrorHandling方法会去判断是否有args参数,有则把参数传到事件处理方法中并执行,没有则只执行事件处理方法。
补充下:
还有一种情况是没有$on监听,子组件向父组件传值,使用$emit,在父组件中的自定义标签中绑定子组件触发的自定义事件,触发相对应的函数,这样父组件也会拿到值,原因在于,在子组件created的时候,Vue实例中的_events对像中已经有值了,key:子组件要触发的自定义的事件名,value:对应父组件要触发的事件处理函数。
export default { name:'homeSon', created() { console.log('son', this._events) // son {handleEmit: Array(1)} }, methods: { click() { this.$emit('handleEmit', '1') } }, }
toArray方法:
在Vue源码中,单独写了这么一个方法,目的:把类数组(如:arguments)或字符串转化为数组,参数二是起始位,如果传start,会从start位置开始截取,截取到最后,左开右闭,i为循环圈数,ret为数组长度是i,有个小细节,之所以要用new Array 去构造一个数组,原因是可以动态传值,且值就是构造出来的数组长度 。
-
Vue设计模式(发布订阅,观察者)
2020-11-23 15:39:36有人说观察者模式是发布订阅的升级版,也有人说发布订阅是进阶版的观察者模式,但是无论是观察者模式还是发布订阅模式,它们都是定义了对象之间的一种一对多的依赖关系。 观察者模式 当目标对象的状态发生改变时,... -
发布订阅者模式以及vue中数据劫持 +结合发布/订阅模式
2019-04-17 03:18:05class Event { constructor (){ this .events = {} ...// vue中数据劫持结合发布/...对发布订阅者的通俗理解 订阅可以理解为寄存,发布理解为寄存的拿出调用,可以寄存很多个,拿出很多个 观察者模式主要是监听 -
发布/订阅者模式,以及vue中更新数据原理解析
2020-05-18 11:19:35要搞清楚vue中的双向数据绑定原理,就必须理解什么是发布、订阅者模式!! 1、首先想好谁是发布者、谁是订阅者(例如NBA专栏就是发布者,NBA球迷就是订阅者) 2、然后给发布者添加一个缓存列表,用于存放回调函数... -
vue Bus总线/发布订阅模式/观察者模式
2019-02-21 10:19:31当然Vue2.0提供了Vuex,但在简单的场景下,可以使用一个空的Vue实例作为中央事件总线。 参考:http://blog.csdn.net/u013034014/article/details/54574989?locationNum=2&fps=1 例子:... -
Vue中的发布订阅模式和数据传递
2020-09-19 01:01:57发布订阅模式中订阅者和发布者没有关联,所以观察者模式中包含了发布订阅模式(watcher和deep) 2、数据传递 /** *1.vue是单向数据流 *2.Vue组件间传值的方式及之间的区别 *3.props和emit父组件向子... -
发布者订阅者模式
2021-01-14 09:49:331 发布订阅模式理解 假定存在一个信号中心,某个任务执行完成就向信号中心发布一个任务,其他任务可以向信号中心订阅这个任务...注:vue中兄弟组件通信过程就是通过发布者订阅者模式实现的。 2 发布订阅者模式代码 ... -
关于观察者,发布订阅者模式与vue
2019-01-21 15:20:00。。 转载于:https://www.cnblogs.com/stephenleee/p/10298939.html -
Vue.js响应式原理(三)——发布订阅模式和观察者模式
2020-09-03 15:02:56订阅者 发布者 信号中心 我们假定,存在一个“信号中心”,某个任务执行完成,就向信号中心发布(publish)一个信号,其他任务可以向信号中心订阅(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就... -
vue.js实现原理 -- 订阅者,发布者模式
2019-03-06 08:22:30zh众所周知,vue2.x版本是通过Object.defineProperty() 种的get进行拦截,set进行发送,其实这只是表面理解,实际运用的是JavaScript的 订阅者、发布者模式。首先,了解一下什么是订阅者、发布者模式 吧。w3c 上是... -
【Vue 响应式原理】发布订阅模式、观察者模式
2020-12-13 01:08:46模拟 Vue 响应式原理。 准备工作 数据驱动 数据响应式 数据模型仅仅是普通的 JavaScript 对象,而当我们修改数据时,视图会进行更新,避免了繁琐的 DOM 操作,提高开发效率。 双向绑定 数据改变,视图改变;... -
结合 Vue 源码谈谈发布-订阅模式
2018-10-21 10:23:23最近的工作学习中接触到了发布-订阅模式。... 订阅函数,添加订阅者,传入发布时要执行的函数,可能会携额外参数 一个缓存订阅者以及订阅者的回调函数的列表 取消订阅(需要分情况讨论) 这么看下来,其实就像 ... -
理解Vue的数据劫持和发布订阅者模式
2019-06-24 21:03:14class Vue { constructor(options) { // 获取到根元素 this.el = document.querySelector(options.el) // #root // 获取到数据 this.data = options.data ...
-
【布道者】Linux极速入门
-
【论文写作】毕业论文降重技巧
-
2014年重庆理工大学《数据库原理及应用》两套期末考试试卷.pdf
-
JAVA01
-
2014年重庆理工大学《软件质量保证与测试》两套期末考试试卷.pdf
-
电池管理系统通信协议.docx
-
Java Scanner的进阶使用 -04天 学习笔记
-
MySQL 数据库权限管理(用户高级管理和精确访问控制)
-
Day2-运算符和变量作业
-
Tushare Day7—— 第19章投资组合理论及拓展
-
超强补丁技术_让EXE启动时自动加载你的DLL(含VC6写的DLL源码模版)
-
2014年重庆理工大学《数据库技术2》期末考试试卷.pdf
-
MySQL 视图
-
CISA培训与认证精讲全套视频.txt
-
FactoryTalk View StudioV10.0备份HMI步骤.docx
-
基于微信的同城小程序、校园二手交易小程序 毕业设计毕设源码使用教程
-
萌新的莫名其妙的错误.txt
-
LeetCode.304 二维区域和检索 - 矩阵不可变
-
2021/03/03学习总结
-
MySQL 设计基础(数据库概论、初探)