序上一篇提到了模塊化的思想,并且引入了按需加載模塊的技術(shù)。然而對(duì)于實(shí)現(xiàn)細(xì)節(jié)卻沒(méi)有講解,因?yàn)榘葱杓虞d有點(diǎn)復(fù)雜,如果不引入觀察者模式的設(shè)計(jì)思想,就會(huì)比較難實(shí)現(xiàn)。 使用觀察者模式,我們實(shí)現(xiàn)dom事件類似的注冊(cè)觸發(fā),這種方式可以很好的解耦,讓模塊間沒(méi)有依賴。我們先討論一下觀察者模式是怎么回事,我們先引入幾個(gè)場(chǎng)景:
我們聯(lián)想一下dom事件,當(dāng)dom被點(diǎn)擊的時(shí)候,如果監(jiān)聽(tīng)了點(diǎn)擊事件,那就觸發(fā)了點(diǎn)擊的回調(diào)方法。如果沒(méi)有注冊(cè),啥事情也不會(huì)發(fā)生。這時(shí)我們假設(shè)兩個(gè)模塊注冊(cè)事件和觸發(fā)事件,試一下來(lái)解決上述的場(chǎng)景問(wèn)題:
通過(guò)以上談?wù)?,如果使用事件的方式是可以完美的解決之前的幾個(gè)場(chǎng)景問(wèn)題。 需求開發(fā)一個(gè)觀察者模式的對(duì)象,該對(duì)象有以下特點(diǎn):
實(shí)現(xiàn)代碼如下 function EventDispatcher() { this.listeners = {}; } EventDispatcher.prototype = { constructor: EventDispatcher, // 訂閱自定義事件 addEventListener: function (type, listener) { this._addEventListener(type, listener); }, // 訂閱某個(gè)類型的事件 _addEventListener: function (type, listener, isOnce) { var listeners = this.listeners; if (typeof listeners[type] == "undefined") { listeners[type] = []; } if (isOnce) listener.once = true; listeners[type].push(listener); }, // 注冊(cè)一次綁定事件,觸發(fā)后自動(dòng)清除 addOnce: function (type, listener) { this._addEventListener(type, listener, true); }, // 移除某個(gè)事件類型的訂閱事件 removeEventListener: function (type, listener) { var listeners = this.listeners; var handlers = listeners[type]; if (Array.isArray(handlers)) { var i = handlers.indexOf(listener); handlers.splice(i, 1); } }, // 根據(jù)名稱移除所有的回調(diào)函數(shù) clearListenerByType: function (type) { var handlers = this.listeners[type]; if (Array.isArray(handlers)) handlers.length = 0; }, // 觸發(fā)某個(gè)事件類型 dispatchEvent: function (event) { var listeners = this.listeners; if (!event.target) { event.target = this; } var handlers = listeners[event.type]; // 如果有事件注冊(cè) if (Array.isArray(handlers)) { var onceIndexs = []; for (var i = 0, len = handlers.length; i < len; i++) { handlers[i](event); if (handlers[i].once) { onceIndexs.push(i); } } // 移除一次觸發(fā)就銷毀的回調(diào)方法 if (onceIndexs.length > 0) { for (var i = onceIndexs.length - 1; i >= 0; i--) { handlers.splice(onceIndexs[i], 1); } } } }, // 銷毀自定義事件 destroy: function () { for (var str in this.listeners) { this.listeners[str].length = 0; delete this.listeners[str]; } } }; 可以通過(guò)繼承或者組合方式來(lái)使用這個(gè)對(duì)象 var a = { eventDispatcher: new EventDispatcher(), attachDiyEvent: function (type, fn) { this.eventDispatcher.addEventListener(type, fn); }, dispatchEvent: function (data) { this.eventDispatcher.dispatchEvent(data) } } 如果每個(gè)模塊都擁有EventDispatcher對(duì)象,就可以使用自定義事件進(jìn)行跨模塊交互了,上一篇說(shuō)的App對(duì)象,Page對(duì)象,以及后面的Component和PopUp對(duì)象,都是間接繼承了EventDispatcher對(duì)象。 實(shí)現(xiàn)按需加載實(shí)現(xiàn)方法 App.require(list, bk),App.define(name, obj), 思路如下:
實(shí)現(xiàn)App.require方法 App.require = function (list, bk) { if (typeof list === "function") return list.call(this); var len = list.length, mods = [], that = this; if (len == 0) bk.call(this); var func = function (obj, index) { mods[index] = obj; if (--len === 0) bk.apply(that, mods); } list.forEach(function (item, index) { // 獲取對(duì)應(yīng)的name的js文件,內(nèi)由App.define(name, obj)方法 var config = that._contains(item); // 這個(gè)方法要定義自定義事件,在App.define觸發(fā)回調(diào),所有觸發(fā)后才會(huì)全部加載完畢 loadUnit._getUnit({ name: item, js: config.js }, function (obj) { func(obj, index); }) }) }; 實(shí)現(xiàn)App.define方法 App.define = function (name, bk) { if (typeof bk === "function") loadUnit.units[name] = bk(); else loadUnit.units[name] = bk; // 告訴模塊我已經(jīng)加載完了。 loadUnit.dispatchEvent({ type: name, detail: { component: loadUnit.units[name] }}) } 通過(guò)繼承的方式,擁有自定義事件的所有方法,實(shí)現(xiàn)LoadUnit對(duì)象 function LoadUnit() { EventDispatcher.call(this); // 存已經(jīng)加載的模塊,如果模塊已加載,就不再加載js,直接返回結(jié)果 this.units = {}; // 存放某個(gè)模塊的加載狀態(tài),如果正在加載中,某個(gè)模塊名為true,否則為false或undefined this.loadObj = {}; } LoadUnit.prototype = create(EventDispatcher.prototype, { constructor: LoadUnit, _getUnit: function (config, next) { var that = this; var name = config.name, url = config.js; // 如果已經(jīng)加載鍋,直接返回加載完畢 if (this.units[name]) next(this.units[name], config); else { // 監(jiān)聽(tīng)觸發(fā)一次的事件,再第二點(diǎn)里面觸發(fā),觸發(fā)后才算加載完畢。 this.addOnce(name, function (obj) { next(obj.detail.component, obj.detail.config || config); }); // 如果不是加載狀態(tài),那就加載js,反之不加載 if (!this.loadObj[name]) { this.loadObj[name] = true; var script = document.createElement("script"), head = document.head; script.src = url; script.onload = function () { that.loadObj[name] = false; } head.appendChild(script); } } } }; // 創(chuàng)建一個(gè)loadUnit,來(lái)管理模塊化 var loadUnit = new LoadUnit(); 后面的pageLoadUnit, componentLoadUnit, popUpLoadUnit都是LoadUnit的實(shí)例對(duì)象。 簡(jiǎn)單案例為了更好的理解將上一章節(jié)的內(nèi)容代碼拷貝下來(lái),并作以下修改
結(jié)語(yǔ)模塊開發(fā)是當(dāng)前web開發(fā)中多人合作開發(fā)的主流方式,如何去管理模塊,以及有序的準(zhǔn)確的加載是非常重要的。自定義事件是框架的核心之一,熟悉原理并熟練使用會(huì)讓項(xiàng)目更容易維護(hù)。 |
|