自己实现一个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月),对少部分内容作了更正,增加了各种例子及解析. 如无特殊说明,本 ...
随机推荐
- react-router 组件式配置与对象式配置小区别
1. react-router 对象式配置 和 组件式配置 组件式配置(Redirect) ----对应---- 对象式配置(onEnter钩子) IndexRedirect -----对应-- ...
- PHP搭建大文件切割分块上传功能
背景 在网站开发中,文件上传是很常见的一个功能.相信很多人都会遇到这种情况,想传一个文件上去,然后网页提示"该文件过大".因为一般情况下,我们都需要对上传的文件大小做限制,防止出现 ...
- Python高手之路【四】python函数装饰器
def outer(func): def inner(): print('hello') print('hello') print('hello') r = func() print('end') p ...
- ABP文档 - EntityFramework 集成
文档目录 本节内容: Nuget 包 DbContext 仓储 默认仓储 自定义仓储 特定的仓储基类 自定义仓储示例 仓储最佳实践 ABP可使用任何ORM框架,它已经内置了EntityFrame(以下 ...
- 【SQLServer】记一次数据迁移-标识重复的简单处理
汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 今天在数据迁移的时候因为手贱遇到一个坑爹问题,发来大家乐乐,也传授新手点经验 迁移惯用就 ...
- ASP.NET Core的路由[4]:来认识一下实现路由的RouterMiddleware中间件
虽然ASP.NET Core应用的路由是通过RouterMiddleware这个中间件来完成的,但是具体的路由解析功能都落在指定的Router对象上,不过我们依然有必要以代码实现的角度来介绍一下这个中 ...
- 编译器开发系列--Ocelot语言5.表达式的有效性检查
本篇将对"1=3""&5"这样无法求值的不正确的表达式进行检查. 将检查如下这些问题.●为无法赋值的表达式赋值(例:1 = 2 + 2)●使用非法的函数 ...
- npm源切换
版权声明:欢迎转载,请附加转载来源:一路博客(http://www.16boke.com) 目录(?)[+] 安装 使用 列出可选的源 切换 增加源 删除源 测试速度 许可 项目主页 我们介绍 ...
- vs生成pro
1.修改.vcxproj文件 <PropertyGroup Label="Globals"> <ProjectGuid>{AAAA4039-13B ...
- MVC5 - ASP.NET Identity登录原理 - Claims-based认证和OWIN
在Membership系列的最后一篇引入了ASP.NET Identity,看到大家对它还是挺感兴趣的,于是来一篇详解登录原理的文章.本文会涉及到Claims-based(基于声明)的认证,我们会详细 ...