vue发布订阅者模式$emit、$on

    家住南京的李先生,最近看上了一套房子,到了售楼处才发现,该楼盘放在早已售罄。好在售楼处工作人员王女士出于好心告知李先生,不久之后还会有一些尾盘推出,开发商在办理相关手续,手续办理好之后便可购买。但到底什么时候能办理好,目前无人知道。于是李先生记下了售楼处电话,每天都会打电话询问是否到了购买时间。除了李先生,还有它的朋友、七大姑八大姨也会每天定点咨询这个问题。一周之后,售楼处王女士决定辞职,原因厌倦了每天回答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 去构造一个数组,原因是可以动态传值,且值就是构造出来的数组长度 。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值