1. 前言
之前研究了下观察者模式,但是事件注册后,都需要手动移除。就在琢磨,能否执行一次后,自动移除。正好vue有这一功能。于是,研究了下相关代码。
2.vue的$once
vue的事件处理代码放在了events.js
文件中。它的$once长这样:
/**
* Adds an `event` listener that will be invoked a single
* time then automatically removed.
*
* @param {String} event
* @param {Function} fn
*/
exports.$once = function (event, fn) {
var self = this
// on函数中先卸载,然后执行了fn
function on () {
self.$off(event, on)
fn.apply(this, arguments)
}
on.fn = fn
this.$on(event, on)
return this // 链式调用
}
代码不多,也比较容易理解。其内部依然用$on
注册的。
- 首先,我们是注册后,执行一次即移除,因此,明面上传入的fn一定不能直接给
$emit()
调用。因为,我们是需要调用一次后就移除,所以,给$emit()
调用的函数,一定有移除事件的处理逻辑。因此,我们看到,内部的$on
注册的是on
函数,该函数内部有移除on
函数的操作。 - 其次,还需要在on函数内部执行一开始传入的fn函数。因此,就有了on函数内部,既要执行fn也要销毁注册的on函数,这样的一种机制。
3.实现自己的$once
const Observer = (function (type, fn) {
const _msg = {};
return {
register: function (type, fn) {
if (!_msg[type]) {
_msg[type] = []
}
_msg[type].push(fn) // 先存储起来,供以后调用。回调
},
fire: function (type, ...args) {
if (_msg[type].length) {
_msg[type].forEach(element => {
element.apply(this, args)
});
} else {
console.log('无该订阅函数');
}
},
remove: function (type, fn) {
if (_msg[type] && _msg[type].includes(fn)) {
_msg[type].splice(_msg[type].indexOf(fn), 1)
} else {
console.log('无该订阅函数');
}
},
registerOnce: function(type, fn) { // 注册一次,fire()后销毁掉
const self = this
function on() {
fn.apply(this, arguments) // 执行的是fn
self.remove(type, on) // 注意,卸载的是on函数(利用了闭包)
}
this.register(type, on) // on里边必须要有销毁函数
}
}
})()
Observer.registerOnce('change', (type, data) => {
console.log(type, data);
})
Observer.fire('change', 'change', 1111)
Observer.fire('change', 'change', 2)
结果如下: