自己实现一个javascript事件模块
nodejs中的事件模块
nodejs中有一个events模块,用来给别的函数对象提供绑定事件、触发事件的能力。这个别的函数的对象,我把它叫做事件宿主对象(非权威叫法),其原理是把宿主函数的原型链指向时间模块的一个对象,做一个函数继承,让宿主函数也拥有处理事件的能力
使用nodejs事件模块的demo如下:
var EventEmitter = require('events');
var util = require('util');
function MyEmitter() {
EventEmitter.call(this);
}
util.inherits(MyEmitter, EventEmitter);
var myEmitter = new MyEmitter();
myEmitter.on("hehe",function(){
console.log("hehe")
})
myEmitter.on("hehe",function(){
console.log("haha")
})
myEmitter.emit("hehe");
console.log(myEmitter.listeners("hehe"));
看node events的api http://nodejs.cn/api/events.html ,一个事件模块需要具备如下的基本功能:
- 添加绑定事件
- 添加绑定一次性事件
- 移除事件监听
- 触发事件
- 其他需要的扩展
事件模块设计
俗话说得好,不会造轮子的车手不是一个老司机!自己实现一个简单的事件系统,实现上面几个基本的功能就行。
捋一捋思路,我们需要先实现一个事件类,用来创建不同名称的事件对象,例如click事件对象,hehe事件对象等,并在对象内部维持一个事件被触发时的处理函数列表。
对于同一个事件宿主对象而言,对它绑定的事件应该有如下特点:
- 同一种类型的事件对象只需要有一个
- 处理函数需要有多个
- 对于同一个事件,重复绑定同一个事件处理函数应该是无效的
于是得到如下结构:
/**
* 定义一个事件单元类
* @param eventname
* @constructor
*/
function EventMeta(eventname){
this.name=eventname;
this.handlerMap={};
this.handlerList=[];
}
每个事件宿主对象都应该自己持有一个map,用来保存自己的事件单元对象,加上上面所说的,绑定,一次性绑定、解绑、触发,宿主函数的原型应该被指向这样一个对象:
var prototype={
addEventListener:addEventListener,
once:once,
removeEventListener:removeEventListener,
trigger:trigger,
getEventHandlerMap:getEventHandlerMap
}
为了让宿主函数拥有这些属性,需要一个函数来为宿主函数指一下prototype属性,这里要注意的是,一定要调用这个函数之后,再扩展宿主函数自身的prototype属性,否则会被覆盖
function czEvent(fn){
if(typeof fn!=="function") return;
fn.prototype=Object.create(prototype);
}
用户使用的时候这样调用一下就可以了
var czEvent=require("../czEvent");
function MyEmitter() {
}
czEvent(MyEmitter);
依次实现事件函数
addEventListener
/**
* 绑定事件监听
* @param type
* @param handler
*/
function addEventListener(eventname,handler){
var EventHandlerMap=this.getEventHandlerMap();
var meta=EventHandlerMap[eventname];
if(!meta){
meta=EventHandlerMap[eventname]=new EventMeta(eventname);
}
var handlerId=handler.__handlerId;
if(!handlerId){
EventHandlerMap.__handlerId++;
handler.__handlerId=EventHandlerMap.__handlerId;
handlerId=handler.__handlerId;
}
//对于同一事件的同一处理函数,不重复绑定
if(!meta.handlerMap[handlerId]){
meta.handlerMap[handlerId]=handler;
meta.handlerList.push(handler);
}
}
once
/**
* 绑定只执行一次的handler
* @param type
* @param handler
*/
function once(eventname,handler){
handler.__once=true;
this.addEventListener(eventname,handler);
}
removeEventListener
/**
* 移除事件监听
* @param type
* @param handdler
*/
function removeEventListener(eventname,handdler){
var EventHandlerMap=this.getEventHandlerMap();
var meta=EventHandlerMap[eventname];
if(!meta) return;
//移除一个handler的绑定
if(handdler && handdler.__handlerId){
var index=meta.handlerList.indexOf(handdler);
if(index>-1){
meta.handlerList.splice(index,1);
delete meta.handlerMap[handdler.__handlerId];
return;
}
}
//移除所有handler
meta.handlerMap={};
meta.handlerList.length=0;
}
trigger
/**
* 触发事件
* @param eventname
*/
function trigger(){
var EventHandlerMap=this.getEventHandlerMap();
var args=[];
var that=this;
for(var i=0;i<arguments.length;i++){
args.push(arguments[i]);
}
var eventname=args.splice(0,1);
var meta=EventHandlerMap[eventname];
if(!meta) return;
var onceHandlerList=[];
//依次同步执行handler
meta.handlerList.forEach(function(handler){
if(handler.__once){
onceHandlerList.push(handler)
}
handler.apply(this,args);
});
//清除绑定为once的事件
onceHandlerList.forEach(function(handler){
that.removeEventListener(eventname,handler);
});
}
getEventHandlerMap
function getEventHandlerMap(){
var EventHandlerMap=this.__EventHandlerMap;
if(EventHandlerMap) return EventHandlerMap;
return this.__EventHandlerMap={__handlerId:0};
}
包装
学(chao)习(xi)一下jquery的包装技术,让模块支持commonJs和amd
/**
* Created by czzou on 2016/6/30.
*/
( function( global, factory ) {
"use strict";
if ( typeof module === "object" && typeof module.exports === "object" ) {
module.exports = factory( global, true );
} else {
factory( global );
}
}(typeof window !== "undefined" ? window : this,function(window,noGlobal){
//把上面的代码copy进来
if ( typeof define === "function" && define.amd ) {
define( "czEvent", [], function() {
return czEvent;
} );
}
if ( !noGlobal ) {
window.czEvent = czEvent;
}
return czEvent;
}))
扩展
如有需求,还可以在此基础上进行一些扩展,比如nodejs的events模块支持的handler函数个数上限、handler函数抛出异常时触发的error事件等,另外,简单改写一下czEvent函数,还可以让事件模块支持给对象赋予事件处理能力的功能
function czEvent(fn,asObj){
if(asObj){
for(var key in prototype){
Object.defineProperty(fn,key,{
value:prototype[key]
})
}
return;
}
if(typeof fn!=="function") return;
fn.prototype=Object.create(prototype);
}
测试
分别测试nodejs的events模块,czEvent的commonJs用法,amd用法以及直接引用用法,demo就不在这里一一列举了~
github地址:https://github.com/zouchengzhuo/czevent
本文转自我的个人站点:http://zoucz.com/blog/2016/07/01/czevent/
自己实现一个javascript事件模块的更多相关文章
- RequireJS 是一个JavaScript模块加载器
RequireJS 是一个JavaScript模块加载器.它非常适合在浏览器中使用, 它非常适合在浏览器中使用,但它也可以用在其他脚本环境, 就像 Rhino and Node. 使用RequireJ ...
- JavaScript事件详解-jQuery的事件实现(三)
正文 本文所涉及到的jQuery版本是3.1.1,可以在压缩包中找到event模块.该篇算是阅读笔记,jQuery代码太长.... Dean Edward的addEvent.js 相对于zepto的e ...
- JavaScript事件详解-Zepto的事件实现(二)【新增fastclick阅读笔记】
正文 作者打字速度实在不咋地,源码部分就用图片代替了,都是截图,本文讲解的Zepto版本是1.2.0,在该版本中的event模块与1.1.6基本一致.此文的fastclick理解上在看过博客园各个大神 ...
- 【模块化编程】理解requireJS-实现一个简单的模块加载器
在前文中我们不止一次强调过模块化编程的重要性,以及其可以解决的问题: ① 解决单文件变量命名冲突问题 ② 解决前端多人协作问题 ③ 解决文件依赖问题 ④ 按需加载(这个说法其实很假了) ⑤ ..... ...
- jQuery源代码学习之九—jQuery事件模块
jQuery事件系统并没有将事件坚挺函数直接绑定在DOM元素上,而是基于事件缓存模块来管理监听函数的. 二.jQuery事件模块的代码结构 //定义了一些正则 // // //jQuery事件对象 j ...
- node的事件模块应用(译)
第一次接触Node.js时,就觉得他只不过是用javascript实现的服务端.但实际上他提供了许多浏览器端不具备的方法,比如EventEmitter类.我们在本文中来学习如何使用EventEmitt ...
- 重温javascript事件机制
以前用过一段时间的jquery感觉太方便,太强大了,各种动画效果,dom事件.创建节点.遍历.控件及UI库,应有尽有:开发文档也很多,网上讨论的问题更是甚多,种种迹象表明jquery是一个出色的jav ...
- JavaScript事件详解-zepto的事件实现
zepto的event 可以结合上一篇JavaScript事件详解-原生事件基础(一)综合考虑源码暂且不表,github里还有中文网站都能下到最新版的zepto.整个event模块不长,274行,我们 ...
- 深入理解JavaScript 事件
本文总结自<JavaScript高级程序设计>以及自己平时的经验,针对较新浏览器以及 DOM3 级事件标准(2016年8月),对少部分内容作了更正,增加了各种例子及解析. 如无特殊说明,本 ...
随机推荐
- In-Memory:内存优化表的事务处理
内存优化表(Memory-Optimized Table,简称MOT)使用乐观策略(optimistic approach)实现事务的并发控制,在读取MOT时,使用多行版本化(Multi-Row ve ...
- Javascript 的执行环境(execution context)和作用域(scope)及垃圾回收
执行环境有全局执行环境和函数执行环境之分,每次进入一个新执行环境,都会创建一个搜索变量和函数的作用域链.函数的局部环境不仅有权访问函数作用于中的变量,而且可以访问其外部环境,直到全局环境.全局执行环境 ...
- Summary of Critical and Exploitable iOS Vulnerabilities in 2016
Summary of Critical and Exploitable iOS Vulnerabilities in 2016 Author:Min (Spark) Zheng, Cererdlong ...
- Linq表达式、Lambda表达式你更喜欢哪个?
什么是Linq表达式?什么是Lambda表达式? 如图: 由此可见Linq表达式和Lambda表达式并没有什么可比性. 那与Lambda表达式相关的整条语句称作什么呢?在微软并没有给出官方的命名,在& ...
- Lambda
Lambda Lambda 表达式是一种可用于创建委托或表达式目录树类型的匿名函数. 通过使用 lambda 表达式,可作为参数传递或作为函数调用值返回的本地函数. Lambda 表达式对于编写 LI ...
- 微信小程序中利用时间选择器和js无计算实现定时器(将字符串或秒数转换成倒计时)
转载注明出处 改成了一个单独的js文件,并修改代码增加了通用性,点击这里查看 今天写小程序,有一个需求就是用户选择时间,然后我这边就要开始倒计时. 因为小程序的限制,所以直接选用时间选择器作为选择定时 ...
- css样式之border-image
border-image-source 属性设置边框的图片的路径[none | <image>] div { border: 20px solid #000; border-image-s ...
- HTML学习笔记
HTML学习笔记 2016年12月15日整理 Chapter1 URL(scheme://host.domain:port/path/filename) scheme: 定义因特网服务的类型,常见的为 ...
- 编译器开发系列--Ocelot语言3.类型名称的消解
"类型名称的消解"即类型的消解.类型名称由TypeRef 对象表示,类型由Type 对象表示.类型名称的消解就是将TypeRef 对象转换为Type 对象. TypeResolve ...
- A*算法应用[转]
转自:http://www.cnblogs.com/zhoug2020/p/3468167.html 这是一篇十分精彩/易懂的博客,感谢原博主!本文通过自己的理解在原博文基础上突出一些重点字眼,句子. ...