详细讲解vue2组件通信(三)——任意组件全局事件总线

理论基础

理论知识点1:中间人

如果我告诉大家,vue官方在Vue构造函数原型上提供了$on()发布,$emit()接收,
$off()删除三个api,如果大家知晓一点设计模式的话,实际上这就是一个标准的
消息发布与订阅设计模式。此时Vue构造函数实例化出来的对象就具备了成为组
件通信中间人的条件。

额外知识补充

为大家简单介绍一下消息发布与订阅模式吧!
发布者调用一个方法实例.$on()将消息添加到消息队列中,并给当前这个消息添加
标识(此过程相当于将制片方电影放在电影院的影片集中,电影的名称就是标识)。
订阅者调用实例.$emit()从消息队列中拿到对应的消息(此过程相当于观众购买对应的电
影片观看电影)。发布者调用实例.$off将消息从消息队列中删除(此过程相当于电影院
下架该电影)

额外知识补充分隔线-----------------------------------------

理论知识点1验证:

mounted(){
    console.log(this);
    console.log(this.__proto__);
}

在这里插入图片描述

理论知识点2:共享中间人

那么问题来了我们怎么可以在所有组件实例中访问到这个中间人?
想不出来没关系,我给大家一个提示。所有的vue组件都是通过构造函数Vue实例化
出来的!!!
那么我们是不是可以将中间人挂载到构造函数Vue的原型上面?这样每一个通过new Vue({})
实例化出来的vue组件都可以访问到这个中间人了?

理论知识点3:函数传值

这个理论知识我在改专栏中的详细vue2组件通信(二)——子传父自定义事件中详细讲解过。
我们可以通过调用函数给函数传递实参来传递数据,但是要注意数据的引用类型。

理论存在实践开始

既然我们要在所有组件实例化之前就得为Vue构造函数的原型上加上我们的中间人,我们
应该在整个项目执行的起始文件也就是主文件main.js中添加。
import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

// new Vue()就是中间人
Vue.prototype.$bus = new Vue();

new Vue({
    render: (h) => h(App),
}).$mount("#app");
<template>
    <div>
        Son
    </div>
</template>

<script>
    export default {
        name: "web-son",
        mounted(){
            console.log(this);
            console.log(this.__proto__);
            console.log(this.$bus);
        }
    }
</script>

<style scoped>

</style>

在这里插入图片描述

优化1:为项目减少一个vue组件的内存消耗

不知道大家是否明白Vue.prototype.$bus = new Vue();意味着我们为整个项目
增加了一个无用的vue组件实例。我们整个项目中这么多的vue组件实例,为什
么我们还要再new一个呢?App组件实例难道不是一个很好的选择吗?

代码优化

import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

new Vue({
    render: (h) => h(App),
    beforeCreate() {
        Vue.prototype.$bus = this;
    }
}).$mount("#app");

在这里插入图片描述

为什么可以这么写,后续我在vue专栏里面单独开一篇博客和大家一块儿讨论main.js
主文件的一些细节点。

优化2:减轻$bus属性值的体积

在这里插入图片描述

因为$bus属性值是App组件实例对象,这个对象有着vue官方给与的众多属性和方法,
但是我们自己清楚我们仅仅只是需要$on,$emit,$off这三个方法罢了。那么为什么我们
不自己写一个呢?不就是一个消息发布订阅设计模式吗?不会就去学,干就完事了。
费了九牛二虎之力,我们终于实现了。

消息发布与订阅模式es6 class版本

class Bus {
    constructor() {
        // 消息队列
        this.messageQueue = {};
    }
    $on(name, fn) {
        /* 判断标识是否存在,存在就操作对应标识已经存在的函数数组,否则就新建
            一个空数组 */
        this.messageQueue[name] = this.messageQueue[name] || [];
        this.messageQueue[name].push(fn);
    }
    $emit(name, args) {
        if (this.messageQueue[name]) {
            /* 循环对应标识的函数数组,并将所有的参数传递给数组中
                  每一项函数执行 */
            this.messageQueue[name].forEach((cb) => cb(args));
        }
    }
    $off(name) {
        if (name) {
            // 删除消息队列中对应的标识属性
            delete this.messageQueue[name];
        } else {
            /* 如果直接调用$bus.$off()不传入name,此时name为undefined,
            消息队列清空 */
            this.messageQueue = {};
        }
    }
}

消息发布与订阅模式es5版本

const Bus = function() {
    let messageQueue = {};
    Bus.prototype.$on = function(name, fn) {
        /* 判断标识是否存在,存在就操作对应标识已经存在的函数数组,否则就新建
                                        一个空数组 */
        messageQueue[name] = messageQueue[name] || [];
        messageQueue[name].push(fn);
    };
    Bus.prototype.$emit = function(name, args) {
        if (messageQueue[name]) {
            /* 循环对应标识的函数数组,并将所有的参数传递给数组中
                                                  每一项函数执行 */
            messageQueue[name].forEach((cb) => cb(args));
        }
    };
    Bus.prototype.$off = function(name) {
        if (name) {
            // 删除消息队列中对应的标识属性
            delete messageQueue[name];
        } else {
            // 如果直接调用$bus.$off()不传入name,此时name为undefined,消息队列清空
            messageQueue = {};
        }
    };
};

如果不想自己手写,你还可以选择当一个调包侠

借助消息订阅与发布第三方库实现
先下载第三方包 npm install pubsub-js --save

消息订阅者

import pubsub from 'pubsub-js'
mounted() {
    this.pubsubGSN = pubsub.subscribe('getSchoolName',(msgName,data) => {
        // 一定要使用箭头函数的形式,不然this指向就是undefined,或者将回调函数写在methodes中
        console.log('感谢'+ msgName + '发布者的发布');
        console.log(this.name + '在' + data + '读书');
    })
},
beforeDestroy() {
    // this.$bus.$off('getSchoolName')
    pubsub.unsubsctibe(this.pubsubGSN)
},

消息的发布者

methods: {
    sendSchoolName(){
        // this.$bus.$emit('getSchoolName',this.name)
        pubsub.publish('getSchoolName',this.name)
    }
},

最终代码与实现效果

main.js

import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;
// class Bus {
//     constructor() {
//         // 消息队列
//         this.messageQueue = {};
//     }
//     $on(name, fn) {
//         /* 判断标识是否存在,存在就操作对应标识已经存在的函数数组,否则就新建
//             一个空数组 */
//         this.messageQueue[name] = this.messageQueue[name] || [];
//         this.messageQueue[name].push(fn);
//     }
//     $emit(name, args) {
//         if (this.messageQueue[name]) {
//             /* 循环对应标识的函数数组,并将所有的参数传递给数组中
//                   每一项函数执行 */
//             this.messageQueue[name].forEach((cb) => cb(args));
//         }
//     }
//     $off(name) {
//         if (name) {
//             // 删除消息队列中对应的标识属性
//             delete this.messageQueue[name];
//         } else {
//             // 如果直接调用$bus.$off()不传入name,此时name为undefined,消息队列清空
//             this.messageQueue = {};
//         }
//     }
// }

const Bus = function() {
    let messageQueue = {};
    Bus.prototype.$on = function(name, fn) {
        /* 判断标识是否存在,存在就操作对应标识已经存在的函数数组,否则就新建
                                        一个空数组 */
        messageQueue[name] = messageQueue[name] || [];
        messageQueue[name].push(fn);
    };
    Bus.prototype.$emit = function(name, args) {
        if (messageQueue[name]) {
            /* 循环对应标识的函数数组,并将所有的参数传递给数组中
                                                  每一项函数执行 */
            messageQueue[name].forEach((cb) => cb(args));
        }
    };
    Bus.prototype.$off = function(name) {
        if (name) {
            // 删除消息队列中对应的标识属性
            delete messageQueue[name];
        } else {
            // 如果直接调用$bus.$off()不传入name,此时name为undefined,消息队列清空
            messageQueue = {};
        }
    };
};

new Vue({
    render: (h) => h(App),
    beforeCreate() {
        Vue.prototype.$bus = new Bus();
    },
}).$mount("#app");

App.vue

<template>
  <div>
    <Son></Son>
  </div>
</template>

<script>
import Son from "./components/Son.vue";
export default {
  name: "App",
  components: {
    Son,
  },
  beforeCreate(){
    this.$bus.$on("sendValue",value => {
      console.log(value);
    })
  },
  beforeDestroy(){
	this.$bus.$off("sendValue");
  }
};
</script>

<style>
</style>

Son.vue

<template>
  <div>Son</div>
</template>

<script>
export default {
  name: "web-son",
  mounted() {
    console.log(this);
    console.log(this.__proto__);
    console.log(this.$bus);
    this.$bus.$emit("sendValue", "data");
  },
};
</script>

<style scoped>
</style>

最终效果
在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LiuJie_Boom

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值