Home > Web Front-end > JS Tutorial > How to implement a custom event mechanism using Javascript

How to implement a custom event mechanism using Javascript

亚连
Release: 2018-06-20 17:02:47
Original
1492 people have browsed it

With the development of web technology, the use of JavaScript to customize objects is becoming more and more frequent. Allowing the objects you create to have event mechanisms and external communication through events can greatly improve development efficiency. The following article mainly introduces you to the relevant information about using Javascript to implement a set of custom event mechanisms. Friends in need can refer to it.

Preface

The event mechanism provides great convenience for our web development, allowing us to specify what operation to perform at any time What operations to perform and what code to execute.

For example, click events are triggered when the user clicks; keydown and keyup events are triggered when the keyboard is pressed or popped up; and in the upload control, events are triggered before the file is added and after the upload is completed.

Because corresponding events will be triggered at the right time, we can specify corresponding processing functions for these events, and we can insert various personalized operations and processing into the original process, making the entire The process becomes richer.

Events such as click, blur, focus, etc. are native events provided directly by the original dom. However, the various events used by some other controls we use are not available in the native dom, such as in the upload control. Usually there are upload start and completion events, so how are these events implemented?

I also want to add a similar event mechanism to the control I develop, how to implement it? Let’s find out.

The functions that events should have

Before implementation, we first analyze the basic functions that the event mechanism should have.

Simply put, events must provide the following functions:

  • Binding events

  • Triggering events

  • Unbinding event

Preliminary preparation

Let’s take a look A characteristic of an event. The event must belong to a certain object. For example: focus and blur events are for DOM elements that can get focus, input events are for input boxes, upload start and upload success are for upload success.

In other words, an event does not exist independently, it requires a carrier. So how do we make events have a carrier? A simple implementation solution is to use events as a base class and inherit this event class where events are needed.

We name the binding event, trigger event, and unbinding event as: on, fire, and off respectively, then we can simply write this event class:

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

Event Binding

First implement event binding. Event binding must specify the event type and event processing function.

So what else is needed besides that? We are a custom event. We don't need to specify whether it is triggered in the bubbling phase or the capture phase like native events. We also don't need to specify which elements to trigger additionally like in jQuery.

This in the event function is generally the current instance. This may not be applicable in some cases. We need to re-specify the context when the event processing function is running.

Therefore, the three parameters when determining event binding are: event type, event processing function, and event processing function execution context.

So what does event binding do? It is actually very simple. Event binding only requires recording the corresponding event name and event processing function.

My implementation is as follows:

{
 /**
  * 绑定事件
  * 
  * @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;
 }
}
Copy after login

Since an event can be bound multiple times and executed in sequence, the processing function storage under all event types uses array.

Event triggering

The basic function of event triggering is to execute the event bound by the user, so it only needs to be checked when the event is triggered. Is there a specified execution function? If so, just call it.

In addition, event triggering is actually the process of executing the user-specified processing function, and many personalized operations are also performed in the user-specified event processing function, so just executing this function is not enough. Necessary information must also be provided for the current function, such as the currently clicked element in the click event, the key code of the current key in the keyboard event, and the current file information in the upload start and upload completion.

So when an event is triggered, the actual parameters of the event processing function must contain the basic information of the current event.

In addition, through the user's operation in the event processing function, the subsequent information may need to be adjusted. For example, in the keydwon event, the user can prohibit the entry of this key. Before the file is uploaded, the user cancels the file in the event. Upload or modify some file information. Therefore the event triggering function should return the event object modified by the user.

My implementation is as follows:

{
 /**
  * 触发事件
  * 
  * @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 < l; ++i) {
   fn = eventArr[i][0];
   event.scope = eventArr[i][1] || this;
   fn.call(event.scope, event);
  }
  return event;
 }
}
Copy after login

The actual parameters given to the event processing function in the above implementation must contain the following information:

  • type: The type of event currently triggered

  • origin: The object to which the current event is bound

  • scope: The execution context of the event processing function

In addition, different events can add different information to this event object when triggered.

关于 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 < eventArr.length; ++i) {
    if (fn === eventArr[i][0]) {
     eventArr.splice(i, 1);
     // 1、找到后不能立即 break 可能存在一个事件一个函数绑定多次的情况
     // 删除后数组改变,下一个仍然需要遍历处理!
     --i;
    }
   }
  }
  return this;
 }
}
Copy after login

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

仅触发一次的事件

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;
 }
}
Copy after login

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

基本雏形

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

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 + &#39;&#39; !== type) {
   console && console.error && console.error(&#39;the first argument type is requird as string&#39;);
   return this;
  }
  if (typeof fn != &#39;function&#39;) {
   console && console.error && console.error(&#39;the second argument fn is requird as function&#39;);
   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 < l; ++i) {
   fn = eventArr[i][0];
   scope = eventArr[i][1];
   if (scope) {
    event.scope = scope;
    fn.call(scope, event);
   } else {
    event.scope = this;
    fn(event);
   }
  }
  return event;
 },
 /**
  * 取消绑定一个事件
  * 
  * @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 < eventArr.length; ++i) {
    if (fn === eventArr[i][0]) {
     eventArr.splice(i, 1);
     // 1、找到后不能立即 break 可能存在一个事件一个函数绑定多次的情况
     // 删除后数组改变,下一个仍然需要遍历处理!
     --i;
    }
   }
  }
  return this;
 },
 /**
  * 绑定一个只执行一次的事件
  * 
  * @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;
 }
};
Copy after login

在自己的控件中使用

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

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

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

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

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

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

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

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

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

var calendar = new Calendar();
calendar.on(&#39;cellRender&#39;, function (e) {
 if(e.isoWeekday > 5 ) {
  e.extraHtml = &#39;<span>假</span>&#39;;
  e.dateCls += &#39; red&#39;;
 } 
});
Copy after login

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

使用jQuery封装animate.css(详细教程)

vue-cli配置文件(详细教程)

使用Vue2.x如何实现JSON树

在Angular4中有关CLI的安装与使用教程

使用JS实现换图时钟(详细教程)

The above is the detailed content of How to implement a custom event mechanism using Javascript. For more information, please follow other related articles on the PHP Chinese website!

source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template