热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

利用Javascript实现一套自定义事件机制

随着web技术发展,使用JavaScript自定义对象愈发频繁,让自己创建的对象也有事件机制,通过事件对外通信,能够极大提高开发效率。下面这篇文章主要给大家介绍了关于利用Javascript实现一套自定义事件机制的相关资料,需要的朋友可以参考下。

前言

事件机制为我们的web开发提供了极大的方便,使得我们能在任意时候指定在什么操作时做什么操作、执行什么样的代码。

如点击事件,用户点击时触发;keydown、keyup事件,键盘按下、键盘弹起时触发;还有上传控件中,文件加入前事件,上传完成后事件。

由于在恰当的时机会有相应的事件触发,我们能为这些事件指定相应的处理函数,就能在原本的流程中插入各种各样的个性化操作和处理,使得整个流程变得更加丰富。

诸如click、blur、focus等事件是原本的dom就直接提供的原生事件,而我们使用的一些其他控件所使用的各种事件则不是原生dom就有的,如上传控件中通常都会有上传开始和完成事件,那么这些事件都是如何实现的呢?

也想在自己的开发的控件中加入类似的事件机制该如何实现呢? 就让我们来一探究竟。

事件应有的功能

在实现之前,我们首先来分析事件机制应该有的基本功能。

简单来说,事件必须要提供以下几种功能:

  • 绑定事件
  • 触发事件
  • 取消绑定事件

前期准备

我们来观察一下事件的一个特征,事件必定是属于某个对象的。如:focus和blur事件是可获取焦点的dom元素的,input事件是输入框的,上传开始和上传成功则是上传成功的。

也就是说,事件不是独立存在的,它需要一个载体。那么我们怎么让事件有一个载体呢?一种简单的实现方案则是,将事件作为一个基类,在需要事件的地方继承这个事件类即可。

我们将绑定事件、触发事件、取消绑定事件分别命名为:on、fire、off,那么我们可以简单写出这个事件类:

function CustomEvent() {
 this._events = {};
}
CustomEvent.prototype = {
 constructor: CustomEvent,
 // 绑定事件
 on: function () {
 },
 // 触发事件
 fire: function () {
 },
 // 取消绑定事件
 off: function () {
 }
};

事件绑定

首先来实现事件的绑定,事件绑定必须要指定事件的类型和事件的处理函数。

那么除此之外还需要什么呢?我们是自定义事件,不需要像原生事件一样指定是冒泡阶段触发还是捕获阶段触发,也不需要像jQuery里一样可以额外指定那些元素触发。

而事件函数里面this一般都是当前实例,这个在某些情况下可能不适用,我们需要重新指定事件处理函数运行时的上下文环境。

因此确定事件绑定时三个参数分别为:事件类型、事件处理函数、事件处理函数执行上下文。

那么事件绑定要干什么呢,其实很简单,事件绑定只用将相应的事件名称和事件处理函数记录下来即可。

我的实现如下:

{
 /**
  * 绑定事件
  * 
  * @param {String} type 事件类型
  * @param {Function} fn 事件处理函数
  * @param {Object} scope 要为事件处理函数绑定的执行上下文
  * @returns 当前实例对象
  */
 on: function (type, fn, scope) {
  if (type + '' !== type) {
   console && console.error && console.error('the first argument type is requird as string');
   return this;
  }
  if (typeof fn != 'function') {
   console && console.error && console.error('the second argument fn is requird as function');
   return this;
  }
  type = type.toLowerCase();

  if (!this._events[type]) {
   this._events[type] = [];
  }
  this._events[type].push(scope ? [fn, scope] : [fn]);
  return this;
 }
}

由于一种事件可以绑定多次,执行时依次执行,所有事件类型下的处理函数存储使用的是数组。

事件触发

事件触发的基本功能就是去执行用户所绑定的事件,所以只用在事件触发时去检查有没有指定的执行函数,如果有则调用即可。

另外事件触发实际就是用户指定的处理函数执行的过程,而能进行很多个性化操作也都是在用户指定的事件处理函数中进行的,因此仅仅是执行这个函数还不够。还必须为当前函数提供必要的信息,如点击事件中有当前被点击的元素,键盘事件中有当前键的键码,上传开始和上传完成中有当前文件的信息。

因此事件触发时,事件处理函数的实参中必须包含当前事件的基本信息。

除此之外通过用户在事件处理函数中的操作,可能需要调整之后的信息,如keydwon事件中用户可以禁止此键的录入,文件上传前,用户在事件中取消此文件的上传或是修改一些文件信息。因此事件触发函数应返回用户修改后的事件对象。

我的实现如下:

{
 /**
  * 触发事件
  * 
  * @param {String} type 触发事件的名称
  * @param {Object} data 要额外传递的数据,事件处理函数参数如下
  * event = {
   // 事件类型
   type: type,
   // 绑定的源,始终为当前实例对象
   origin: this,
   // 事件处理函数中的执行上下文 为 this 或用户指定的上下文对象
   scope :this/scope
   // 其他数据 为fire时传递的数据
  }
  * @returns 事件对象
  */
 fire: function (type, data) {
  type = type.toLowerCase();
  var eventArr = this._events[type];
  var fn,
   // event = {
   //  // 事件类型
   //  type: type,
   //  // 绑定的源
   //  origin: this,
   //  // scope 为 this 或用户指定的上下文,
   //  // 其他数据 
   //  data: data,
   //  // 是否取消
   //  cancel: false
   // };
   // 上面对自定义参数的处理不便于使用 将相关属性直接合并到事件参数中
   event = $.extend({
    // 事件类型
    type: type,
    // 绑定的源
    origin: this,
    // scope 为 this 或用户指定的上下文,
    // 其他数据 
    // data: data,
    // 是否取消
    cancel: false
   }, data);
  if (!eventArr) {
   return event;
  }
  for (var i = 0, l = eventArr.length; i 

上面实现中给事件处理函数的实参中必定包含以下信息:

  • type : 当前触发的事件类型
  • origin : 当前事件绑定到的对象
  • scope : 事件处理函数的执行上下文

此外不同事件在各种的触发时可为此事件对象中加入各自不同的信息。

关于 Object.assign(target, ...sources) 是ES6中的一个方法,作用是将所有可枚举属性的值从一个或多个源对象复制到目标对象,并返回目标对象,类似于大家熟知的$.extend(target,..sources) 方法。

事件取消

事件取消中需要做的就是已经绑定的事件处理函数移除掉即可。

实现如下:

{
 /**
  * 取消绑定一个事件
  * 
  * @param {String} type 取消绑定的事件名称
  * @param {Function} fn 要取消绑定的事件处理函数,不指定则移除当前事件类型下的全部处理函数
  * @returns 当前实例对象
  */
 off: function (type, fn) {
  type = type.toLowerCase();
  var eventArr = this._events[type];
  if (!eventArr || !eventArr.length) return this;
  if (!fn) {
   this._events[type] = eventArr = [];
  } else {
   for (var i = 0; i 

此处实现类似原生的事件取消绑定,如果指定了事件处理函数则移除指定事件的指定处理函数,如果省略事件处理函数则移除当前事件类型下的所有事件处理函数。

仅触发一次的事件

jQuery中有一个 one 方法,它所绑定的事件仅会执行一次,此方法在一些特定情况下非常有用,不需要用户手动取消绑定这个事件。

这里的实现也非常简单,只用在触发这个事件时取消绑定即可。

实现如下:

{
 /**
  * 绑定一个只执行一次的事件
  * 
  * @param {String} type 事件类型
  * @param {Function} fn 事件处理函数
  * @param {Object} scope 要为事件处理函数绑定的执行上下文
  * @returns 当前实例对象
  */
 one: function (type, fn, scope) {
  var that = this;
  function nfn() {
   // 执行时 先取消绑定
   that.off(type, nfn);
   // 再执行函数
   fn.apply(scope || that, arguments);
  }
  this.on(type, nfn, scope);
  return this;
 }
}

原理则是不把用户指定的函数直接绑定上去,而是生成一个新的函数,并绑定,此函数执行时会先取消绑定,再执行用户指定的处理函数。

基本雏形

到此,一套完整的事件机制就已经完成了,完整代码如下:

function CustomEvent() {
 this._events = {};
}
CustomEvent.prototype = {
 constructor: CustomEvent,
 /**
  * 绑定事件
  * 
  * @param {String} type 事件类型
  * @param {Function} fn 事件处理函数
  * @param {Object} scope 要为事件处理函数绑定的执行上下文
  * @returns 当前实例对象
  */
 on: function (type, fn, scope) {
  if (type + '' !== type) {
   console && console.error && console.error('the first argument type is requird as string');
   return this;
  }
  if (typeof fn != 'function') {
   console && console.error && console.error('the second argument fn is requird as function');
   return this;
  }
  type = type.toLowerCase();

  if (!this._events[type]) {
   this._events[type] = [];
  }
  this._events[type].push(scope ? [fn, scope] : [fn]);

  return this;
 },
 /**
  * 触发事件
  * 
  * @param {String} type 触发事件的名称
  * @param {Anything} data 要额外传递的数据,事件处理函数参数如下
  * event = {
   // 事件类型
   type: type,
   // 绑定的源,始终为当前实例对象
   origin: this,
   // 事件处理函数中的执行上下文 为 this 或用户指定的上下文对象
   scope :this/scope
   // 其他数据 为fire时传递的数据
  }
  * @returns 事件对象
  */
 fire: function (type, data) {
  type = type.toLowerCase();
  var eventArr = this._events[type];
  var fn, scope,
   event = Object.assign({
    // 事件类型
    type: type,
    // 绑定的源
    origin: this,
    // scope 为 this 或用户指定的上下文,
    // 是否取消
    cancel: false
   }, data);

  if (!eventArr) return event;

  for (var i = 0, l = eventArr.length; i 

在自己的控件中使用

上面已经实现了一套事件机制,我们如何在自己的事件中使用呢。

比如我写了一个日历控件,需要使用事件机制。

function Calendar() {
 // 加入事件机制的存储的对象
 this._event = {};
 // 日历的其他实现
}
Calendar.prototype = {
 constructor:Calendar,
 on:function () {},
 off:function () {},
 fire:function () {},
 one:function () {},
 // 日历的其他实现 。。。
}

以上伪代码作为示意,仅需在让控件继承到on、off 、fire 、one等方法即可。但是必须保证事件的存储对象_events 必须是直接加载实例上的,这点需要在继承时注意,Javascript中实现继承的方案太多了。

上面为日历控件Calendar中加入了事件机制,之后就可以在Calendar中使用了。

如在日历开发时,我们在日历的单元格渲染时触发cellRender事件。

// 每天渲染时发生 还未插入页面
var renderEvent = this.fire('cellRender', {
 // 当天的完整日期
 date: date.format('YYYY-MM-DD'),
 // 当天的iso星期
 isoWeekday: day,
 // 日历dom
 el: this.el,
 // 当前单元格
 tdEl: td,
 // 日期文本
 dateText: text.innerText,
 // 日期class
 dateCls: text.className,
 // 需要注入的额外的html
 extraHtml: '',
 isHeader: false
});

在事件中,我们将当前渲染的日期、文本class等信息都提供给用户,这样用户就可以绑定这个事件,在这个事件中进行自己的个性阿化处理了。

如在渲染时,如果是周末则插入一个"假"的标识,并让日期显示为红色。

var calendar = new Calendar();
calendar.on('cellRender', function (e) {
 if(e.isoWeekday > 5 ) {
  e.extraHtml = '';
  e.dateCls += ' red';
 } 
});

在控件中使用事件机制,即可简化开发,使得流程易于控制,还可为实际使用时提供非常丰富的个性化操作,快快用起来吧。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。


推荐阅读
  • Ihavethefollowingonhtml我在html上有以下内容<html><head><scriptsrc..3003_Tes ... [详细]
  • 本文介绍了在满足特定条件时如何在输入字段中使用默认值的方法和相应的代码。当输入字段填充100或更多的金额时,使用50作为默认值;当输入字段填充有-20或更多(负数)时,使用-10作为默认值。文章还提供了相关的JavaScript和Jquery代码,用于动态地根据条件使用默认值。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了解决IE678伪类不兼容问题的方法,包括少用CSS3和HTML5独有的属性,使用CSS hacker,使用last-child清除浮动、批量添加标签、去掉list item最后一个的border-right等技巧。同时还介绍了使用after清除浮动时加上IE独有属性zoom:1的处理方法。另外,本文还提到可以使用jQuery代替批量添加标签的功能,以及使用负边距和CSS2选择器element+element去掉list item最后一个的border-right的方法。 ... [详细]
  • 延迟注入工具(python)的SQL脚本
    本文介绍了一个延迟注入工具(python)的SQL脚本,包括使用urllib2、time、socket、threading、requests等模块实现延迟注入的方法。该工具可以通过构造特定的URL来进行注入测试,并通过延迟时间来判断注入是否成功。 ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • Javascript中带有加号 - 减号(±)的极坐标曲线方程 - Polar curve equation with plus-minus sign (±) in Javascript
    IamtryingtodrawpolarcurvesonHTMLcanvasusingJavascript.WhatshouldIdowhenIwanttoco ... [详细]
  • 本文介绍了一个免费的asp.net控件,该控件具备数据显示、录入、更新、删除等功能。它比datagrid更易用、更实用,同时具备多种功能,例如属性设置、数据排序、字段类型格式化显示、密码字段支持、图像字段上传和生成缩略图等。此外,它还提供了数据验证、日期选择器、数字选择器等功能,以及防止注入攻击、非本页提交和自动分页技术等安全性和性能优化功能。最后,该控件还支持字段值合计和数据导出功能。总之,该控件功能强大且免费,适用于asp.net开发。 ... [详细]
  • 本文介绍了DataTables插件的官方网站以及其基本特点和使用方法,包括分页处理、数据过滤、数据排序、数据类型检测、列宽度自动适应、CSS定制样式、隐藏列等功能。同时还介绍了其易用性、可扩展性和灵活性,以及国际化和动态创建表格的功能。此外,还提供了参数初始化和延迟加载的示例代码。 ... [详细]
  • 获取ul中第一个li元素的五种方法和多个ul中第一个li元素的四种方法
    本文介绍了获取ul中第一个li元素的五种方法和多个ul中第一个li元素的四种方法,包括使用jQuery的选择器和遍历方法。通过这些方法,可以方便地获取到所需的元素,并进行相应的操作。 ... [详细]
  • 如何压缩网站页面以减少页面加载时间
    本文介绍了影响网站打开时间的两个因素,即网页加载速度和网站页面大小。重点讲解了如何通过压缩网站页面来减少页面加载时间。具体包括图片压缩、Javascript压缩、CSS压缩和HTML压缩等方法,并推荐了相应的压缩工具。此外,还提到了一款Google Chrome插件——网页加载速度分析工具Speed Tracer。 ... [详细]
  • mui框架offcanvas侧滑超出部分隐藏无法滚动如何解决
    web前端|js教程off-canvas,部分,超出web前端-js教程mui框架中off-canvas侧滑的一个缺点就是无法出现滚动条,因为它主要用途是设置类似于qq界面的那种格 ... [详细]
  • 03Spring使用注解方式注入
    基于注解的DI注入1.导包环境搭建:导入aop包(spring-aop-4.1.6.RELEASE.jar)2.创建类3.创建spring.xml配置文件(必须在src目录下)该配 ... [详细]
  • 前言对于从事技术的人员来说ajax是这好东西,都会使用,而且乐于使用。但对于新手,开发一个ajax实例,还有是难度的,必竟对于他们这是新东西。leo开发一个简单的ajax实例,用的是 ... [详细]
  • 表单提交前的最后验证:通常在表单提交前,我们必须确认用户是否都把必须填选的做了,如果没有,就不能被提交到服务器,这里我们用到表单的formname.submit()看演示,其实这个对于我们修炼道 ... [详细]
author-avatar
honey热情沙漠
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有