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