序上一篇提到了模块化的思想,并且引入了按需加载模块的技术。然而对于实现细节却没有讲解,因为按需加载有点复杂,如果不引入观察者模式的设计思想,就会比较难实现。 使用观察者模式,我们实现dom事件类似的注册触发,这种方式可以很好的解耦,让模块间没有依赖。我们先讨论一下观察者模式是怎么回事,我们先引入几个场景:
我们联想一下dom事件,当dom被点击的时候,如果监听了点击事件,那就触发了点击的回调方法。如果没有注册,啥事情也不会发生。这时我们假设两个模块注册事件和触发事件,试一下来解决上述的场景问题:
通过以上谈论,如果使用事件的方式是可以完美的解决之前的几个场景问题。 需求开发一个观察者模式的对象,该对象有以下特点:
实现代码如下 function EventDispatcher() { this.listeners = {}; } EventDispatcher.prototype = { constructor: EventDispatcher, // 订阅自定义事件 addEventListener: function (type, listener) { this._addEventListener(type, listener); }, // 订阅某个类型的事件 _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); }, // 注册一次绑定事件,触发后自动清除 addOnce: function (type, listener) { this._addEventListener(type, listener, true); }, // 移除某个事件类型的订阅事件 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); } }, // 根据名称移除所有的回调函数 clearListenerByType: function (type) { var handlers = this.listeners[type]; if (Array.isArray(handlers)) handlers.length = 0; }, // 触发某个事件类型 dispatchEvent: function (event) { var listeners = this.listeners; if (!event.target) { event.target = this; } var handlers = listeners[event.type]; // 如果有事件注册 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); } } // 移除一次触发就销毁的回调方法 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]; } } }; 可以通过继承或者组合方式来使用这个对象 var a = { eventDispatcher: new EventDispatcher(), attachDiyEvent: function (type, fn) { this.eventDispatcher.addEventListener(type, fn); }, dispatchEvent: function (data) { this.eventDispatcher.dispatchEvent(data) } } 如果每个模块都拥有EventDispatcher对象,就可以使用自定义事件进行跨模块交互了,上一篇说的App对象,Page对象,以及后面的Component和PopUp对象,都是间接继承了EventDispatcher对象。 实现按需加载实现方法 App.require(list, bk),App.define(name, obj), 思路如下:
实现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) { // 获取对应的name的js文件,内由App.define(name, obj)方法 var config = that._contains(item); // 这个方法要定义自定义事件,在App.define触发回调,所有触发后才会全部加载完毕 loadUnit._getUnit({ name: item, js: config.js }, function (obj) { func(obj, index); }) }) }; 实现App.define方法 App.define = function (name, bk) { if (typeof bk === "function") loadUnit.units[name] = bk(); else loadUnit.units[name] = bk; // 告诉模块我已经加载完了。 loadUnit.dispatchEvent({ type: name, detail: { component: loadUnit.units[name] }}) } 通过继承的方式,拥有自定义事件的所有方法,实现LoadUnit对象 function LoadUnit() { EventDispatcher.call(this); // 存已经加载的模块,如果模块已加载,就不再加载js,直接返回结果 this.units = {}; // 存放某个模块的加载状态,如果正在加载中,某个模块名为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; // 如果已经加载锅,直接返回加载完毕 if (this.units[name]) next(this.units[name], config); else { // 监听触发一次的事件,再第二点里面触发,触发后才算加载完毕。 this.addOnce(name, function (obj) { next(obj.detail.component, obj.detail.config || config); }); // 如果不是加载状态,那就加载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); } } } }; // 创建一个loadUnit,来管理模块化 var loadUnit = new LoadUnit(); 后面的pageLoadUnit, componentLoadUnit, popUpLoadUnit都是LoadUnit的实例对象。 简单案例为了更好的理解将上一章节的内容代码拷贝下来,并作以下修改
结语模块开发是当前web开发中多人合作开发的主流方式,如何去管理模块,以及有序的准确的加载是非常重要的。自定义事件是框架的核心之一,熟悉原理并熟练使用会让项目更容易维护。 |
|