Maison  >  Article  >  interface Web  >  Comment implémenter un mécanisme d'événement personnalisé à l'aide de Javascript

Comment implémenter un mécanisme d'événement personnalisé à l'aide de Javascript

亚连
亚连original
2018-06-20 17:02:471396parcourir

Avec le développement de la technologie Web, l'utilisation de JavaScript pour personnaliser des objets devient de plus en plus fréquente. Permettre aux objets que vous créez d'avoir des mécanismes d'événements et une communication externe via des événements peut grandement améliorer l'efficacité du développement. L'article suivant vous présente principalement les informations pertinentes sur l'utilisation de Javascript pour implémenter un ensemble de mécanismes d'événements personnalisés. Les amis dans le besoin peuvent s'y référer.

Préface

Le mécanisme d'événements offre une grande commodité pour notre développement Web, nous permettant de spécifier quelle opération effectuer à tout moment Quelles opérations effectuer effectuer et quel code exécuter.

Par exemple, les événements de clic sont déclenchés lorsque l'utilisateur clique ; les événements keydown et keyup sont déclenchés lorsque le clavier est enfoncé ou affiché et dans le contrôle de téléchargement, les événements sont déclenchés avant l'ajout du fichier et après l'ajout du fichier ; le téléchargement est terminé.

Étant donné que les événements correspondants seront déclenchés au bon moment, nous pouvons spécifier les fonctions de traitement correspondantes pour ces événements, et nous pouvons insérer diverses opérations et traitements personnalisés dans le processus d'origine, rendant l'ensemble du processus plus riche.

Les événements tels que le clic, le flou, le focus, etc. sont des événements natifs directement fournis par le dom d'origine, tandis que certains des différents événements utilisés par d'autres contrôles que nous utilisons ne sont pas disponibles dans le dom natif, comme dans le contrôle de téléchargement Habituellement, il y a des événements de début et de fin de téléchargement, alors comment ces événements sont-ils implémentés ?

Je souhaite également ajouter un mécanisme d'événement similaire au contrôle que je développe. Comment l'implémenter ? Découvrons-le.

Les fonctions que les événements devraient avoir

Avant la mise en œuvre, nous analysons d'abord les fonctions de base que devrait avoir le mécanisme d'événements.

En termes simples, les événements doivent fournir les fonctions suivantes :

  • Lier un événement

  • Événement déclencheur

  • Dissocier l'événement

Préparation

Jetons un coup d'œil Une caractéristique d'un événement. l'événement doit appartenir à un certain objet. Par exemple : les événements de focus et de flou sont destinés aux éléments DOM qui peuvent obtenir le focus, les événements d'entrée sont destinés aux zones de saisie, le début du téléchargement et la réussite du téléchargement sont destinés au succès du téléchargement.

Autrement dit, un événement n'existe pas de manière indépendante, il nécessite un porteur. Alors, comment pouvons-nous faire en sorte que les événements aient un transporteur ? Une solution d'implémentation simple consiste à utiliser les événements comme classe de base et à hériter de cette classe d'événements là où des événements sont nécessaires.

Nous nommons l'événement de liaison, l'événement déclencheur et l'événement de déliaison comme : on, fire, off respectivement, puis nous pouvons simplement écrire cette classe d'événement :

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

Liaison d'événement

Implémentez d'abord la liaison d'événement. La liaison d'événement doit spécifier le type d'événement et la fonction de traitement des événements.

Alors, de quoi d'autre avez-vous besoin à part ça ? Nous sommes un événement personnalisé. Nous n'avons pas besoin de spécifier s'il est déclenché lors de la phase de bouillonnement ou de la phase de capture comme les événements natifs. Nous n'avons pas non plus besoin de spécifier quels éléments déclencher en plus comme dans jQuery.

Il s'agit généralement de l'instance actuelle dans la fonction d'événement. Cela peut ne pas être applicable dans certains cas. Nous devons re-spécifier le contexte dans lequel la fonction de traitement d'événement est exécutée.

Par conséquent, les trois paramètres permettant de déterminer la liaison d'événement sont : le type d'événement, la fonction de traitement d'événement et le contexte d'exécution de la fonction de traitement d'événement.

Alors, à quoi sert la liaison d'événement ? C'est en fait très simple. La liaison d'événement nécessite uniquement l'enregistrement du nom de l'événement correspondant et de la fonction de traitement de l'événement.

Mon implémentation est la suivante :

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

Puisqu'un événement peut être lié plusieurs fois, il est exécuté en séquence pendant l'exécution, et les fonctions de traitement sous toutes les types d'événements sont stockés et utilisés dans un tableau.

Déclenchement d'événement

La fonction de base du déclenchement d'événement est d'exécuter l'événement lié par l'utilisateur, il ne doit donc être vérifié que lorsque l'événement est déclenché. Existe-t-il une fonction d'exécution spécifiée ? Si oui, appelez-la.

De plus, le déclenchement d'événements est en fait le processus d'exécution de la fonction de traitement spécifiée par l'utilisateur, et de nombreuses opérations personnalisées sont également effectuées dans la fonction de traitement d'événements spécifiée par l'utilisateur, donc la simple exécution de cette fonction ne suffit pas. Les informations nécessaires doivent également être fournies pour la fonction actuelle, telles que l'élément actuellement cliqué dans l'événement de clic, le code de la touche actuelle dans l'événement clavier et les informations sur le fichier actuel au début et à la fin du téléchargement.

Par conséquent, lorsqu'un événement est déclenché, les paramètres réels de la fonction de traitement d'événement doivent contenir les informations de base de l'événement en cours.

De plus, grâce à l'opération de l'utilisateur dans la fonction de traitement des événements, les informations ultérieures peuvent devoir être ajustées. Par exemple, dans l'événement keydwon, l'utilisateur peut interdire la saisie de cette clé avant que le fichier ne soit enregistré. téléchargé, l'utilisateur annule le fichier en cas de téléchargement ou de modification de certaines informations du fichier. Par conséquent, la fonction de déclenchement d'événement doit renvoyer l'objet événement modifié par l'utilisateur.

Mon implémentation est la suivante :

{
 /**
  * 触发事件
  * 
  * @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;
 }
}

Les paramètres réels donnés à la fonction de traitement d'événements dans l'implémentation ci-dessus doivent contenir les informations suivantes :

  • type : le type d'événement actuellement déclenché

  • origine : l'objet auquel l'événement en cours est lié

  • portée : la fonction de gestionnaire d'événements Contexte d'exécution

De plus, différents événements peuvent ajouter différentes informations à cet objet événement lorsqu'ils sont déclenchés.

关于 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;
 }
}

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

仅触发一次的事件

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 + &#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;
 }
};

在自己的控件中使用

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

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

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(&#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
});

在事件中,我们将当前渲染的日期、文本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;;
 } 
});

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

相关文章:

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

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

使用Vue2.x如何实现JSON树

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

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn