JS中自定義事件與觀察者模式詳解
一、前言
觀察者模式 也稱發(fā)布-訂閱模式 、 模型-視圖模式 。當對象間存在一對多關(guān)系時,則使用觀察者模式(Observer Pattern)。比如,當一個對象被修改時,則會自動通知依賴它的對象。觀察者模式屬于行為型模式。
觀察者模式: 類似我們在微信平臺訂閱了公眾號 , 當它有新的文章發(fā)表后,就會推送給我們所有訂閱的人。
我們作為訂閱者不必每次都去查看這個公眾號有沒有新文章發(fā)布,公眾號作為發(fā)布者會在合適時間通知我們。
我們與公眾號之間不再強耦合在一起。公眾號不關(guān)心誰訂閱了它, 不管你是男是女還是寵物狗,它只需要定時向所有訂閱者發(fā)布消息即可。
二、觀察者模式優(yōu)缺點
優(yōu)點:
- 可以廣泛應(yīng)用于異步編程,它可以代替我們傳統(tǒng)的回調(diào)函數(shù)。
- 我們不需要關(guān)注對象在異步執(zhí)行階段的內(nèi)部狀態(tài),我們只關(guān)心事件完成的時間點。
- 取代對象之間硬編碼通知機制,一個對象不必顯式調(diào)用另一個對象的接口,而是松耦合的聯(lián)系在一起。
- 降低了目標與觀察者之間的耦合關(guān)系,兩者之間是抽象耦合關(guān)系。符合依賴倒置原則。
缺點:
- 如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
- 如果在觀察者和觀察目標之間有循環(huán)依賴的話,觀察目標會觸發(fā)它們之間進行循環(huán)調(diào)用,可能導(dǎo)致系統(tǒng)崩潰。
- 觀察者模式?jīng)]有相應(yīng)的機制讓觀察者知道所觀察的目標對象是怎么發(fā)生變化的,而僅僅只是知道觀察目標發(fā)生了變化。
三、代碼實現(xiàn)
- Event簡易版
var Event = (function() {
let list = {};
let listen = function(key, fn) {
(list[key] || (list[key] = [])).push(fn);
};
let remove = function(key, fn) {
let fns = list[key];
if (!fns) return;
if (!fn) {
fns && (fns.length = 0);
} else {
for (let i = fns.length - 1; i >= 0; i--) {
fns[i] === fn && (fns.splice(i, 1));
}
}
};
let trigger = function() {
let keys = [].shift.call(arguments);
let fns = list[keys];
if (!fns || fns.length === 0) return;
for (let i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments);
}
};
return {
listen,
remove,
trigger
};
})();
// 使用
function log(name) {
console.log(name)
}
Event.listen('to-do', log) // 訂閱事件
Event.trigger('to-do', '111') // 111
Event.trigger('to-do', '222') // 222
Event.remove('to-do', log) // 移除事件
Event.trigger('to-do', '333') // 不執(zhí)行
- 類似Nodejs的EventEmitter模擬版
function EventEmitter() {
this._maxListeners = 10;
this._events = Object.create(null);
}
// 向事件隊列添加事件
// prepend為true表示向事件隊列頭部添加事件
EventEmitter.prototype.addListener = function (type, listener, prepend) {
if (!this._events) {
this._events = Object.create(null);
}
if (this._events[type]) {
if (prepend) {
this._events[type].unshift(listener);
} else {
this._events[type].push(listener);
}
} else {
this._events[type] = [listener];
}
};
// 移除某個事件
EventEmitter.prototype.removeListener = function (type, listener) {
if (Array.isArray(this._events[type])) {
if (!listener) {
delete this._events[type]
} else {
this._events[type] = this._events[type].filter(e => e !== listener && e.origin !== listener)
}
}
};
// 向事件隊列添加事件,只執(zhí)行一次
EventEmitter.prototype.once = function (type, listener) {
const only = (...args) => {
listener.apply(this, args);
this.removeListener(type, listener);
}
only.origin = listener;
this.addListener(type, only);
};
// 執(zhí)行某類事件
EventEmitter.prototype.emit = function (type, ...args) {
if (Array.isArray(this._events[type])) {
this._events[type].forEach(fn => {
fn.apply(this, args);
});
}
};
// 設(shè)置最大事件監(jiān)聽個數(shù)
EventEmitter.prototype.setMaxListeners = function (count) {
this.maxListeners = count;
};
// 使用
var emitter = new EventEmitter();
var onceListener = function (args) {
console.log('我只能被執(zhí)行一次', args, this);
}
var listener = function (args) {
console.log('我是一個listener', args, this);
}
emitter.once('click', onceListener);
emitter.addListener('click', listener);
emitter.emit('click', '參數(shù)');
emitter.emit('click');
emitter.removeListener('click', listener);
emitter.emit('click');
四、DOM自定義事件API
Event構(gòu)造器
// 創(chuàng)建
var customEvent = new Event('custom-event');
// 監(jiān)聽
window.addEventListener('custom-event', function(e) {
// do something ...
console.log(`事件對象:`, e)
})
// 觸發(fā)
window.dispatchEvent(customEvent)
CustomEvent構(gòu)造器,可傳遞參數(shù)。
// 創(chuàng)建
var customEvent = new CustomEvent('custom-event', {
data: {
name: 'Tom',
age: 18,
other: 'otherMessage',
}
});
// 監(jiān)聽
var eBox = document.getElementById('box')
eBox.addEventListener('custom-event', function(e) {
// do something ...
console.log(`傳入的數(shù)據(jù):`, e.data)
})
// 觸發(fā) 兼容寫法
if(window.dispatchEvent) {
eBox.dispatchEvent(customEvent);
} else {
// IE8
eBox.fireEvent(customEvent);
}以上就是JS中自定義事件與觀察者模式詳解的詳細內(nèi)容,更多關(guān)于JS自定義事件觀察者模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS精髓原型鏈繼承及構(gòu)造函數(shù)繼承問題糾正
這篇文章主要為大家介紹了JS精髓原型鏈繼承及構(gòu)造函數(shù)繼承問題糾正,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06
JavaScript實現(xiàn)文本轉(zhuǎn)換為文件示例詳解
這篇文章主要為大家介紹了JavaScript實現(xiàn)文本轉(zhuǎn)換為文件示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08
關(guān)于JavaScript防抖與節(jié)流的區(qū)別與實現(xiàn)
這篇文章主要介紹關(guān)于JavaScript防抖與節(jié)流的區(qū)別與實現(xiàn),防抖就是用戶多次觸發(fā)事件,在用戶一直觸發(fā)事件中,事件不會執(zhí)行,只有在用戶停止觸發(fā)事件一段時間之后再執(zhí)行這個事件一次,二節(jié)流是用戶多次觸發(fā)事件,具體詳情一i起來學(xué)習下面文章內(nèi)容吧2021-10-10
JavaScript面試數(shù)組index和對象key問題詳解
這篇文章主要為大家介紹了JavaScript面試數(shù)組index和對象key問題詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12

