我们先写一个简单的发布订阅:
<!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 = [] // 缓存列表 存放订阅函数
<span class="token comment">// 添加订阅者</span> shopObj<span class="token punctuation">.</span><span class="token function-variable function">listen</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">fn</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> shopObj<span class="token punctuation">.</span>list<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>fn<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// 发布消息</span> shopObj<span class="token punctuation">.</span><span class="token function-variable function">trigger</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">,</span> fn<span class="token punctuation">;</span> fn <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">[</span>i<span class="token operator">++</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token function">fn</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> arguments<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// 添加第一个订阅者</span> shopObj<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">color<span class="token punctuation">,</span> size</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">订阅者1:颜色是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>color<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, 尺码是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>size<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// 添加第二个订阅者</span> shopObj<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">color<span class="token punctuation">,</span> size</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">订阅者2:颜色是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>color<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, again尺码是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>size<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// 发布第一个消息 本意是要通知第1个订阅者 橘黄色 42尺码的货到了</span> shopObj<span class="token punctuation">.</span><span class="token function">trigger</span><span class="token punctuation">(</span><span class="token string">'orange'</span><span class="token punctuation">,</span> <span class="token number">42</span><span class="token punctuation">)</span> <span class="token comment">// 发布第二个消息 本意是要通知第2个订阅者 黑色 39尺码的货到了</span> shopObj<span class="token punctuation">.</span><span class="token function">trigger</span><span class="token punctuation">(</span><span class="token string">'black'</span><span class="token punctuation">,</span> <span class="token number">39</span><span class="token punctuation">)</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
</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 = [] // 缓存列表 存放订阅函数
<span class="token comment">// 添加订阅者</span> shopObj<span class="token punctuation">.</span><span class="token function-variable function">listen</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">key<span class="token punctuation">,</span> fn</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 没有对应key的话就创建一个数组</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token comment">// 然后再把回调函数push进list[key]</span> shopObj<span class="token punctuation">.</span>list<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>fn<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// 发布消息</span> shopObj<span class="token punctuation">.</span><span class="token function-variable function">trigger</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token comment">// 获取对应的key</span> <span class="token keyword">var</span> key <span class="token operator">=</span> <span class="token class-name">Array</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function">shift</span><span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span>arguments<span class="token punctuation">)</span> <span class="token comment">// 这里因为arguments是一个类数组 没有shift方法 需要借用</span> <span class="token keyword">var</span> fns <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>fns <span class="token operator">||</span> fns<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">,</span> fn<span class="token punctuation">;</span> fn <span class="token operator">=</span> fns<span class="token punctuation">[</span>i<span class="token operator">++</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token function">fn</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> arguments<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// 添加第一个订阅者dean</span> shopObj<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token string">'dean'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">color<span class="token punctuation">,</span> size</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">订阅者dean:颜色是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>color<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, 尺码是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>size<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">的鞋子到货了!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// 添加第二个订阅者jing</span> shopObj<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token string">'jing'</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">color<span class="token punctuation">,</span> size</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">订阅者jing:颜色是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>color<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, again尺码是</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${<!-- --></span>size<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">的鞋子到货了!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token comment">// 发布第一个消息 通知第dean 橘黄色 42尺码的鞋子到货了</span> shopObj<span class="token punctuation">.</span><span class="token function">trigger</span><span class="token punctuation">(</span><span class="token string">'dean'</span><span class="token punctuation">,</span> <span class="token string">'orange'</span><span class="token punctuation">,</span> <span class="token number">42</span><span class="token punctuation">)</span> <span class="token comment">// 发布第二个消息 通知第jing 黑色 39尺码的货到了</span> shopObj<span class="token punctuation">.</span><span class="token function">trigger</span><span class="token punctuation">(</span><span class="token string">'jing'</span><span class="token punctuation">,</span> <span class="token string">'black'</span><span class="token punctuation">,</span> <span class="token number">39</span><span class="token punctuation">)</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
</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 }
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> fn<span class="token punctuation">;</span> fn <span class="token operator">=</span> fns<span class="token punctuation">[</span>i<span class="token operator">++</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token function">fn</span><span class="token punctuation">(</span><span class="token operator">...</span>arguments<span class="token punctuation">)</span> <span class="token punctuation">}</span>
},
// 取消订阅
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 }
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> fn<span class="token punctuation">;</span> fn <span class="token operator">=</span> fns<span class="token punctuation">[</span>i<span class="token operator">++</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token function">fn</span><span class="token punctuation">(</span><span class="token operator">...</span>arguments<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function-variable function">remove</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">key<span class="token punctuation">,</span> fn</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">var</span> fns <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>list<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>fns<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">return</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> fns<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">var</span> _fn <span class="token operator">=</span> fns<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>_fn <span class="token operator">==</span> fn<span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> fns<span class="token punctuation">.</span><span class="token function">splice</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
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 }
<span class="token comment">// for (var i=0, fn; fn = fns[i++];) {<!-- --></span> <span class="token comment">// fn(...arguments)</span> <span class="token comment">// }</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator"><</span> fns<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> <span class="token keyword">var</span> fn <span class="token operator">=</span> fns<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token function">fn</span><span class="token punctuation">(</span><span class="token operator">...</span>arguments<span class="token punctuation">)</span> <span class="token punctuation">}</span>
},
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
效果: