序上一篇提到了模塊化的思想,并且引入了按需加載模塊的技術(shù)。然而對(duì)于實(shí)現(xiàn)細(xì)節(jié)卻沒(méi)有講解,因?yàn)榘葱杓虞d有點(diǎn)復(fù)雜,如果不引入觀(guān)察者模式的設(shè)計(jì)思想,就會(huì)比較難實(shí)現(xiàn)。 使用觀(guān)察者模式,我們實(shí)現(xiàn)dom事件類(lèi)似的注冊(cè)觸發(fā),這種方式可以很好的解耦,讓模塊間沒(méi)有依賴(lài)。我們先討論一下觀(guān)察者模式是怎么回事,我們先引入幾個(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)題。 需求開(kāi)發(fā)一個(gè)觀(guān)察者模式的對(duì)象,該對(duì)象有以下特點(diǎn):
實(shí)現(xiàn)代碼如下 function EventDispatcher() {
this.listeners = {};
}
EventDispatcher.prototype = {
constructor: EventDispatcher,
// 訂閱自定義事件
addEventListener: function (type, listener) {
this._addEventListener(type, listener);
},
// 訂閱某個(gè)類(lèi)型的事件
_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è)事件類(lèi)型的訂閱事件
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ù)名稱(chēng)移除所有的回調(diào)函數(shù)
clearListenerByType: function (type) {
var handlers = this.listeners[type];
if (Array.isArray(handlers)) handlers.length = 0;
},
// 觸發(fā)某個(gè)事件類(lèi)型
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ā)就銷(xiāo)毀的回調(diào)方法
if (onceIndexs.length > 0) {
for (var i = onceIndexs.length - 1; i >= 0; i--) {
handlers.splice(onceIndexs[i], 1);
}
}
}
},
// 銷(xiāo)毀自定義事件
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è)方法要定義自定義事件,在A(yíng)pp.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ǔ)模塊開(kāi)發(fā)是當(dāng)前web開(kāi)發(fā)中多人合作開(kāi)發(fā)的主流方式,如何去管理模塊,以及有序的準(zhǔn)確的加載是非常重要的。自定義事件是框架的核心之一,熟悉原理并熟練使用會(huì)讓項(xiàng)目更容易維護(hù)。 |
|
|
來(lái)自: 行者花雕 > 《待分類(lèi)》