JavaScript组件设计思想(二)
来源:互联网 发布:人工智能 图书馆 编辑:程序博客网 时间:2024/06/06 23:53
2016年3月份曾写过一篇文章《JavaScript组件设计思想》其中描述了一些实现组件化的方式,以及降低各组件耦合度的说明。其中“事件机制”不失为好的选择!经过了更多实践给我带来了更多的思考。
事件实现,日常开发中我们经常需要这样的事件去完成某些操作:
function Event() { this._events = Object.create(null); // 存储所有事件}Event.prototype = { constructor: Event, // 监听事件 key:事件类型,listener:事件处理函数(可以同时绑定多个不同类型事件) on: function (event, fn) { var eventTarget = this; (eventTarget._events[event] || (eventTarget._events[event] = [])).push(fn); return eventTarget; }, // 只监听一次 once: function (event, fn) { var eventTarget = this; function on() { eventTarget.off(event, on); fn.apply(eventTarget, arguments); } on.fn = fn; eventTarget.on(event, on); return eventTarget }, // 移除指定类型事件 off: function (event, fn) { var eventTarget = this; // 移除所有事件 if (!arguments.length) { eventTarget._events = Object.create(null); return eventTarget } var cbs = eventTarget._events[event]; if (!cbs) { return eventTarget } // 移除所有指定事件 if (arguments.length === 1) { eventTarget._events[event] = null; return eventTarget } // 移除特定回调的指定事件 var cb; var i = cbs.length; while (i--) { cb = cbs[i]; if (cb === fn || cb.fn === fn) { cbs.splice(i, 1); break } } return eventTarget }, // 触发对应类型的事件 emit: function (event) { var eventTarget = this; var cbs = eventTarget._events[event]; if (cbs) { cbs = cbs.length > 1 ? Array.prototype.slice.call(cbs) : cbs; var args = Array.prototype.slice.call(arguments, 1); for (var i = 0, l = cbs.length; i < l; i++) { cbs[i].apply(eventTarget, args); } } return eventTarget }};
开发中,我们经常会遇到在用户登录成功后我们需要初始化“header”、“toolbar”、“menu”等情况。通常的做法是在登录成功回调中去调用对应模块的初始化函数,然而这样各个模块之间的耦合度太高,不符合开发规范。所以,常借用事件去实现;然而有时,“toolbar”中修改了某项状态,我们也要通知其他模块做相应的操作。这时候我们就需要让“toolbar”也具有事件行为。假设,现在我们具有一个Person类,要求其要具备事件行为。下述为几种实现方式:
一、传统事件方式
公用“公共”的事件,实现简单,多模块同时具备事件时比较难维护!
// 创建Event实例var event = new Event();function Person(name){ this.name = name;}Person.prototype.sayName = function(){ console.log(this.name); // 触发事件(Event实例) event.emit("Person.sayName", this,name);};// 监听事件(Event实例)event.on("Person.sayName", function(args){ console.log(args.name + " emit Person.sayName event!");});var p = new Person("ligang");p.sayName();
二、伪类继承方式
Person是Event的一种?关系比较复杂,容易产生歧义,不易理解!
// 继承事件Person.prototype = new Event(); // Object.create(Event.prototype);Person.prototype.constructor = Person;Person.prototype.sayName = function(){ console.log(this.name); // 触发事件(当前Person实例) this.emit("Person.sayName", this,name);};var p = new Person("ligang");// 监听事件(当前Person实例)p.on("Person.sayName", function(args){ console.log(args.name + " emit Person.sayName event!");});p.sayName();// person属于Event一种实现console.log(p instanceof Person); // trueconsole.log(p instanceof Event); // true
三、混入方式
mixin(des, src)接受两个参数:接受者和提供者。函数的目的是将提供者所有的可枚举属性复制给接受者。
function mixin(des, src){ for(var prop in src){ if(src.hasOwnProperty(prop)){ des[prop] = src[prop]; } } return des;}
ES6中新增方法Object.assign()
可以达到同样的目的,其用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。会忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
注意,mixin和Object.assign实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
var src = {a: {b: 1}, c: 1};var des = {};Object.assign(des, src);src.a.b = 2;src.c = 2;console.log(des.a.b); // 2console.log(des.c); // 1
混入方式实现思路清晰,但需要将Event构造函数修改一下,因为其只能扩展自身属性和方法。
function Event() {}Event.prototype = { constructor: Event, // 监听事件 key:事件类型,listener:事件处理函数(可以同时绑定多个不同类型事件) on: function (event, fn) { var eventTarget = this; // 将事件存储提取到on函数中 if(!this._events){ this._events = Object.create(null); // 存储所有事件 } (eventTarget._events[event] || (eventTarget._events[event] = [])).push(fn); return eventTarget; }, ...
function Person(name){ this.name = name;}// mixin(Person.prototype, Event.prototype);Object.assign(Person.prototype, Event.prototype);mixin(Person.prototype, { constructor: Person, sayName: function() { console.log(this.name); this.emit("Person.sayName", this, name); }});var p = new Person("ligang");p.on("Person.sayName", function(args){ console.log(args.name + " emit Person.sayName event!");});p.sayName();console.log(p instanceof Person); // trueconsole.log(p instanceof Event); // false
四、项目中如何选择
上述描述的“传统方式”、“伪类继承”、“混入方式”三种实现在项目中,我们该如何抉择呢?个人认为,在项目中,我们至少需要两种事件:“总线事件”和“支线事件”。
总线事件:处理各个模块之间的发布、订阅(全局)!
支线事件:具体模块内部的发布、订阅(局部)!
// 模块间,通过globalEvent发布、订阅事件的机制var globalEvent = new Event();globalEvent.on("login", ...);globalEvent.emit("login");
// 模块内,通过“混入”方式具备发布、订阅事件的机制var HeaderSetting = function(){ ... }Object.assign(HeaderSetting.prototype, Event.prototype);var hs = new HeaderSetting();hs.on("add", function(){ ...});hs.addHeader = function(){ this.emit("add"); ...}
- JavaScript组件设计思想(二)
- JavaScript组件设计思想(二)
- JavaScript组件设计思想
- JavaScript组件设计思想
- JQuery设计思想(二)
- Android组件设计思想
- JavaScript的语源(设计思想)来源
- JavaScript 设计思想
- spring事务管理器设计思想(二)
- Java(二)--OOP设计思想
- fpga设计思想(二):有限状态机
- 设计思想二
- 容器及组件设计思想
- 组件/模块设计思想总结
- JavaScript的OO思想(二)
- JavaScript设计模式(二)
- COM组件设计与应用(二)
- COM组件设计与应用(二)
- boost之unordered_map
- spring 4.3.5导入commons-logging的jar包后报NoClassDefFoundError
- 【ife】任务二十五:JavaScript和树(四)
- 分区表 主键全局索引和分区索引区别
- linux服务器ssh、公匙和密钥实战详解
- JavaScript组件设计思想(二)
- Populating Next Right Pointers in Each Node
- Powershell命令杂记
- //编写函数fun,其功能是将字符串末尾的*号删掉。如:******ABC*D*B*BDD***** //删掉之后:******ABC*D*B*BDD
- 王爽汇编语言课程设计1
- Caffe Blob Dtype理解
- list.add(rs.getInt())报错的问题
- 【网络流24题】1.飞行员配对方案问题
- Android中双击图片放大 PhotoView