ホームページ  >  記事  >  ウェブフロントエンド  >  Javascript を使用してカスタム イベント メカニズムを実装する方法

Javascript を使用してカスタム イベント メカニズムを実装する方法

亚连
亚连オリジナル
2018-06-20 17:02:471432ブラウズ

Web テクノロジーの発展に伴い、JavaScript を使用してオブジェクトをカスタマイズすることがますます頻繁になってきており、作成したオブジェクトにもイベント メカニズムを持たせ、イベントを通じて外部と通信することで、開発効率を大幅に向上させることができます。次の記事では、JavaScript を使用して一連のカスタム イベント メカニズムを実装することに関する関連情報を主に紹介します。必要な方は参照してください。

前書き

イベント メカニズムは Web 開発に非常に便利で、実行する操作や実行するコードをいつでも指定できます。

たとえば、クリック イベントはユーザーがクリックするとトリガーされ、キーダウン イベントとキーアップ イベントはキーボードが押されるかポップアップされるとトリガーされ、アップロード コントロールでは、ファイルが追加される前とアップロードが完了した後にイベントがトリガーされます。

対応するイベントが適切なタイミングでトリガーされるため、これらのイベントに対応する処理関数を指定することができ、元のプロセスにさまざまなパーソナライズされた操作や処理を挿入することができ、プロセス全体をより効率的にすることができます。

クリック、ブラー、フォーカスなどのイベントは、元の DOM によって直接提供されるネイティブ イベントですが、通常、使用する他のコントロールで使用されるさまざまなイベントの一部は、ネイティブ DOM では利用できません。アップロード コントロールの開始イベントと終了イベントはどのように実装されるのでしょうか?

私が開発したコントロールに同様のイベントメカニズムを追加したいのですが、どうやって実装すればよいですか? 確認してみましょう。

イベントが持つべき機能

実装前に、まずイベント機構が持つべき基本的な機能を分析します。

簡単に言えば、イベントは次の機能を提供する必要があります:

  • イベントのバインド

  • イベントのトリガー

  • イベントのバインド解除

準備

やってみよう イベントの特徴を観察するイベントは特定のオブジェクトに属している必要があります。たとえば、focus イベントと Blur イベントはフォーカスを取得できる DOM 要素用、input イベントは入力ボックス用、アップロード開始とアップロード成功はアップロード成功用です。

言い換えれば、イベントは独立して存在するのではなく、キャリアを必要とします。では、イベントにキャリアを持たせるにはどうすればよいでしょうか?簡単な実装ソリューションは、イベントを基本クラスとして使用し、イベントが必要な場所でこのイベント クラスを継承することです。

バインディング イベント、トリガー イベント、バインド解除イベントにそれぞれ on、fire、off という名前を付けます。その後、次のイベント クラスを簡単に記述できます:

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

イベント バインディング

最初にイベント バインディングを実装し、イベント バインディングを実装する必要があります。イベントの種類とイベント処理関数を指定します。

それで、それ以外に何が必要ですか?カスタム イベントです。ネイティブ イベントのように、バブリング フェーズでトリガーされるかキャプチャ フェーズでトリガーされるかを指定する必要はありません。また、jQuery のように追加でトリガーする要素を指定する必要もありません。

イベント関数のこれは通常、現在のインスタンスです。イベント処理関数の実行中には、コンテキストを再指定する必要がある場合があります。

つまり、イベント バインディングを決定するときの 3 つのパラメーターは、イベント タイプ、イベント処理関数、イベント処理関数の実行コンテキストです。

それでは、イベント バインディングは何を行うのでしょうか? イベント バインディングは実際には、対応するイベント名とイベント処理関数を記録するだけです。

私の実装は次のとおりです:

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

上記の実装でイベント処理関数に指定される実際のパラメータには、次の情報が含まれている必要があります:

  • type : 現在トリガーされているイベントのタイプ

  • origin :現在のイベントは、オブジェクト

  • スコープ: イベント処理関数

の実行コンテキストにバインドされています。 さらに、さまざまなイベントがトリガーされたときに、このイベント オブジェクトにさまざまな情報を追加できます。

关于 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实现换图时钟(详细教程)

以上がJavascript を使用してカスタム イベント メカニズムを実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。