发布可以早于订阅吗?
可以
例如在 js 设计模式一书中的例子,只截取了关键的代码
Event.trigger('click', 1)
Event.listen('click', function(a) {
console.log(a) // 输出: 1
})
var Event = (function() {
var global = this,
Event,
_default = 'default'
Event = (function() {
var _listen,
_trigger,
_remove,
_shift = Array.prototype.shift,
_unshift = Array.prototype.unshift,
_create,
each = function(ary, fn) {
var ret
for (var i = 0, l = ary.length; i < l; i++) {
var n = ary[i]
ret = fn.call(n, i, n)
}
return ret
}
// 内部的listen
_listen = function(key, fn, cache) {
if (!cache[key]) {
cache[key] = []
}
cache[key].push(fn)
}
// 内部的listen
_trigger = function() {
console.log('_trigger', arguments)
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret,
stack = cache[key]
if (!stack || !stack.length) {
return
}
return each(stack, function() {
return this.apply(_self, args)
})
}
_create = function(namespace) {
var cache = {},
offlineStack = [], // 离线事件
ret = {
listen: function(key, fn, last) {
_listen(key, fn, cache)
if (offlineStack === null) {
return
}
if (last === 'last') {
offlineStack.length && offlineStack.pop()()
} else {
each(offlineStack, function() {
this()
})
}
offlineStack = null
},
trigger: function() {
var fn,
args,
_self = this
_unshift.call(arguments, cache)
args = arguments
fn = function() {
return _trigger.apply(_self, args)
}
if (offlineStack) {
return offlineStack.push(fn)
}
return fn()
}
}
return ret
}
return {
create: _create,
listen: function(key, fn, last) {
var event = this.create()
event.listen(key, fn, last)
},
trigger: function() {
var event = this.create()
event.trigger.apply(this, arguments)
}
}
})()
return Event
})()
解析
1. 发布事件
// 发布事件
Event.trigger('click', 1)
/*
1. 调用内部的 ret._trigger 方法
2. 但此时offlineStack的离线消息栈中为[],所以走下面的代码,把缓存 trigger的 fn方法。
*/
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret,
stack = cache[key]
fn = function() {
return _trigger.apply(_self, args)
}
if (offlineStack) {
return offlineStack.push(fn)
}
2. 订阅事件
// 订阅事件
Event.listen('click', function(a) {
console.log(a) // 输出: 1
})
/*
* 1. 调用内部的 ret._listen方法,将cache里面缓存订阅事件的 key-fn。例如'click'和 'function()'
*/
_listen = function(key, fn, cache) {
if (!cache[key]) {
cache[key] = []
}
cache[key].push(fn)
}
/**
* 2. 继续走ret.listen方法,将offlineStack遍历(each方法),取出离线消息,然后执行。
*/
listen: function(key, fn, last) {
_listen(key, fn, cache)
if (offlineStack === null) {
return
}
if (last === 'last') {
offlineStack.length && offlineStack.pop()()
} else {
each(offlineStack, function() {
this() // 执行fn()
})
}
offlineStack = null
}
/**
* 3. 由于在ret.trriger中 fn其实是个闭包,引用着args变量。所以根据闭包的特性,当cache对象的属性改变时,trriger中的arguments也跟着改变。
*/
trigger: function() {
var fn,
args,
_self = this
_unshift.call(arguments, cache)
args = arguments
fn = function() {
console.log(args)
// 此时这里的args从 发布阶段的 { '0': {}, '1': 'click', '2': 1 }
// 变为 { '0': { click: [ [Function] ] }, '1': 'click', '2': 1 }
return _trigger.apply(_self, args)
}
if (offlineStack) {
return offlineStack.push(fn)
}
return fn()
}
/**
* 4. 最后就简单了,就是执行订阅事件里面的function。在_trigger函数中。
* 遍历stack
*/
_trigger = function() {
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret,
stack = cache[key]
if (!stack || !stack.length) {
return
}
return each(stack, function() {
return this.apply(_self, args)
})
}
总结
- 看了此书这章节收益颇多,发布早于订阅,可以实现类似于
qq的离线消息
- 其实这部分的代码重点是一个闭包的运用,也正好对闭包进行一个实践吧。