我们先写一个简单的发布订阅:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>发布订阅模式</title>
</head>
<body>
<script type="text/javascript">
var shopObj = {} // 定义发布者 卖家对象
shopObj.list = [] // 缓存列表 存放订阅函数
// 添加订阅者
shopObj.listen = function (fn) {
shopObj.list.push(fn)
}
// 发布消息
shopObj.trigger = function () {
for (var i=0, fn; fn = this.list[i++];) {
fn.apply(this, arguments)
}
}
// 添加第一个订阅者
shopObj.listen(function(color, size) {
console.log(`订阅者1:颜色是${color}, 尺码是${size}`)
})
// 添加第二个订阅者
shopObj.listen(function(color, size) {
console.log(`订阅者2:颜色是${color}, again尺码是${size}`)
})
// 发布第一个消息 本意是要通知第1个订阅者 橘黄色 42尺码的货到了
shopObj.trigger('orange', 42)
// 发布第二个消息 本意是要通知第2个订阅者 黑色 39尺码的货到了
shopObj.trigger('black', 39)
</script>
</body>
</html>
可以看到,已经可以订阅消息,并且发布消息,但是有个问题,就是发布消息的时候,把消息发送给所有订阅者了,怎么才能解决这个问题呢?
我们回忆一下,我们在使用发布订阅的时候,是不是这样使用的,要传入一个标识:
然后在触发时候,和订阅时候传入的标识对应上,是不是就能解决这个问题了
我们这样改造: 添加订阅时候,传入一个标识符key, 如果缓存列表里不存在对应的list[key]
,那么就创建一个对应的list[key]
初始值是一个空数组[]
如果有的话,直接把回调函数push进入对应的list[key]
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>发布订阅模式2-优化</title>
</head>
<body>
<script type="text/javascript">
var shopObj = {} // 定义发布者 卖家对象
shopObj.list = [] // 缓存列表 存放订阅函数
// 添加订阅者
shopObj.listen = function (key, fn) {
// 没有对应key的话就创建一个数组
if (!this.list[key]) {
this.list[key] = []
}
// 然后再把回调函数push进list[key]
shopObj.list[key].push(fn)
}
// 发布消息
shopObj.trigger = function () {
// 获取对应的key
var key = Array.prototype.shift.call(arguments) // 这里因为arguments是一个类数组 没有shift方法 需要借用
var fns = this.list[key]
if (!fns || fns.length === 0) {
return
}
for (var i=0, fn; fn = fns[i++];) {
fn.apply(this, arguments)
}
}
// 添加第一个订阅者dean
shopObj.listen('dean', function(color, size) {
console.log(`订阅者dean:颜色是${color}, 尺码是${size}的鞋子到货了!`)
})
// 添加第二个订阅者jing
shopObj.listen('jing', function(color, size) {
console.log(`订阅者jing:颜色是${color}, again尺码是${size}的鞋子到货了!`)
})
// 发布第一个消息 通知第dean 橘黄色 42尺码的鞋子到货了
shopObj.trigger('dean', 'orange', 42)
// 发布第二个消息 通知第jing 黑色 39尺码的货到了
shopObj.trigger('jing', 'black', 39)
</script>
</body>
</html>
我们优化一下代码,再添加一个取消订阅:
var event = {
list: [],
listen: function(key, fn) {
// 没有对应key的话就创建一个数组
if (!this.list[key]) {
this.list[key] = []
}
// 然后再把回调函数push进list[key]
shopObj.list[key].push(fn)
},
trigger: function() {
// 获取对应的key
var key = Array.prototype.shift.call(arguments)
var fns = this.list[key]
if (!fns || fns.length === 0) {
return
}
for (var i = 0, fn; fn = fns[i++];) {
fn(...arguments)
}
},
// 取消订阅
remove: function(key, fn) {
var fns = this.list[key]
if (!fns) {
return false
} else {
for (var i = fns.length - 1; i >= 0; i--) {
var _fn = fns[i]
if (_fn == fn) {
fns.splice(i, 1)
}
}
}
}
}
代码还可以用立即执行函数封装一下:
// 代码封装成立即执行函数
var Event = (function() {
var list = [],
listen,
trigger,
remove;
listen = function(key, fn) {
// 没有对应key的话就创建一个数组
if (!this.list[key]) {
this.list[key] = []
}
// 然后再把回调函数push进list[key]
shopObj.list[key].push(fn)
},
trigger = function() {
// 获取对应的key
var key = Array.prototype.shift.call(arguments)
var fns = this.list[key]
if (!fns || fns.length === 0) {
return
}
for (var i = 0, fn; fn = fns[i++];) {
fn(...arguments)
}
},
remove = function(key, fn) {
var fns = this.list[key]
if (!fns) {
return false
} else {
for (var i = fns.length - 1; i >= 0; i--) {
var _fn = fns[i]
if (_fn == fn) {
fns.splice(i, 1)
}
}
}
}
return {
listen,
trigger,
remove
}
})()
我们再看一下,发布订阅模式的使用场景: 解决异步调用中的强耦合问题:
这种强耦合代码,缺点是,修改一个地方,其他地方都要跟着一起修改
使用发布订阅模式修改:
我们接着在vue里面自己手写一个简易的发布订阅模块:
Main.vue
<template>
<div>
<el-input type="text" v-model="name" placeholder="Input name"></el-input>
<el-button type="primary" @click="handlePub">Pub</el-button>
<Son />
</div>
</template>
<script>
import Son from './Son.vue'
import pubsub from './utils/pubsub2'
export default {
name: 'Pub',
components: {
Son
},
data () {
return {
name: ''
}
},
methods: {
handlePub () {
pubsub.trigger('item', this.name)
}
}
}
</script>
<style>
</style>
Son.vue
<template>
<div>
<h1>{{content}}</h1>
</div>
</template>
<script>
import pubsub from './utils/pubsub2'
export default {
name: 'Son',
data () {
return {
content: ''
}
},
mounted () {
let _this = this // 这里要注意this指向 或者用箭头函数
pubsub.listen('item', function(data) {
console.log('data:', data)
console.log('this:',this)
_this.content = data
})
}
}
</script>
<style>
</style>
pubsub2.js
var Event = (function(){
var list = [],
listen,
trigger,
remove;
listen = function (key, fn) {
// 没有对应key的话就创建一个数组
if (!list[key]) {
list[key] = []
}
// 然后再把回调函数push进list[key]
list[key].push(fn)
},
trigger = function () {
// 获取对应的key
var key = Array.prototype.shift.call(arguments)
var fns = list[key]
if (!fns || fns.length === 0) {
return
}
// for (var i=0, fn; fn = fns[i++];) {
// fn(...arguments)
// }
for (var i = 0; i< fns.length; i++) {
var fn = fns[i]
fn(...arguments)
}
},
remove = function (key, fn) {
var fns = list[key]
if (!fns) {
return false
} else {
for (var i = fns.length - 1; i >= 0; i--) {
var _fn = fns[i]
if (_fn == fn) {
fns.splice(i, 1)
}
}
}
}
return {
listen,
trigger,
remove
}
})()
export default Event
效果: