详解JavaScript中的自定义事件编写


Posted in Javascript onMay 10, 2016

我们可以自定义事件来实现更灵活的开发,事件用好了可以是一件很强大的工具,基于事件的开发有很多优势(后面介绍)。

与自定义事件的函数有 Event、CustomEvent 和 dispatchEvent。

直接自定义事件,使用 Event 构造函数:

var event = new Event('build');

// Listen for the event.
elem.addEventListener('build', function (e) { ... }, false);

// Dispatch the event.
elem.dispatchEvent(event);

CustomEvent 可以创建一个更高度自定义事件,还可以附带一些数据,具体用法如下:

var myEvent = new CustomEvent(eventname, options);

其中 options 可以是:

{
  detail: {
    ...
  },
  bubbles: true,
  cancelable: false
}

其中 detail 可以存放一些初始化的信息,可以在触发的时候调用。其他属性就是定义该事件是否具有冒泡等等功能。

内置的事件会由浏览器根据某些操作进行触发,自定义的事件就需要人工触发。dispatchEvent 函数就是用来触发某个事件:

element.dispatchEvent(customEvent);

上面代码表示,在 element 上面触发 customEvent 这个事件。结合起来用就是:

// add an appropriate event listener
obj.addEventListener("cat", function(e) { process(e.detail) });

// create and dispatch the event
var event = new CustomEvent("cat", {"detail":{"hazcheeseburger":true}});
obj.dispatchEvent(event);

使用自定义事件需要注意兼容性问题,而使用 jQuery 就简单多了:

// 绑定自定义事件
$(element).on('myCustomEvent', function(){});

// 触发事件
$(element).trigger('myCustomEvent');
此外,你还可以在触发自定义事件时传递更多参数信息:

$( "p" ).on( "myCustomEvent", function( event, myName ) {
 $( this ).text( myName + ", hi there!" );
});
$( "button" ).click(function () {
 $( "p" ).trigger( "myCustomEvent", [ "John" ] );
});

JavaScript 自定义事件就是有别于如 click, submit 等标准事件的自行定制的事件,在叙述自定义事件有何好处之前,先来看一个自定义事件的例子:

<div id="testBox"></div>

// 创建事件
var evt = document.createEvent('Event');
// 定义事件类型
evt.initEvent('customEvent', true, true);
// 在元素上监听事件
var obj = document.getElementById('testBox');
obj.addEventListener('customEvent', function(){
  console.log('customEvent 事件触发了');
}, false);

具体效果可以查看 Demo,在 console 中输入 obj.dispatchEvent(evt),可以看到 console 中输出“customEvent 事件触发了”,表示自定义事件成功触发。

在这个过程中,createEvent 方法创建了一个空事件 evt,然后使用 initEvent 方法定义事件的类型为约定好的自定义事件,再对相应的元素进行监听,接着,就是使用 dispatchEvent 触发事件了。

没错,自定义事件的机制如普通事件一样——监听事件,写回调操作,触发事件后执行回调。但不同的是,自定义事件完全由我们控制触发时机,这就意味着实现了一种 JavaScript 的解耦。我们可以把多个关联但逻辑复杂的操作利用自定义事件的机制灵活地控制好。

当然,可能你已经猜到了,上面的代码在低版本的 IE 中并不生效,事实上在 IE8 及以下版本的 IE 中并不支持 createEvent(),而有 IE 私有的 fireEvent() 方法,但遗憾的是,fireEvent 只支持标准事件的触发。因此,我们只能使用一个特殊而简单的方法触发自定义事件。

// type 为自定义事件,如 type = 'customEvent',callback 为开发者实际定义的回调函数
obj[type] = 0;
obj[type]++;
 
obj.attachEvent('onpropertychange', function(event){
  if( event.propertyName == type ){
    callback.call(obj);
  }
});

这个方法的原理实际上是在 DOM 中增加一个自定义属性,同时监听元素的 propertychange 事件,当 DOM 的某个属性的值发生改变时就会触发 propertychange 的回调,再在回调中判断发生改变的属性是否为我们的自定义属性,若是则执行开发者实际定义的回调。从而模拟了自定义事件的机制。

为了使到自定义事件的机制能配合标准事件的监听和模拟触发,这里给出一个完整的事件机制,这个机制支持标准事件和自定义事件的监听,移除监听和模拟触发操作。需要注意的是,为了使到代码的逻辑更加清晰,这里约定自定义事件带有 'custom' 的前缀(例如:customTest,customAlert)。

/**
 * @description 包含事件监听、移除和模拟事件触发的事件机制,支持链式调用
 *
 */
 
(function( window, undefined ){
 
var Ev = window.Ev = window.$ = function(element){
 
  return new Ev.fn.init(element);
};
 
// Ev 对象构建
 
Ev.fn = Ev.prototype = {
 
  init: function(element){
 
    this.element = (element && element.nodeType == 1)? element: document;
  },
 
  /**
   * 添加事件监听
   * 
   * @param {String} type 监听的事件类型
   * @param {Function} callback 回调函数
   */
 
  add: function(type, callback){
 
    var _that = this;
     
    if(_that.element.addEventListener){
       
      /**
       * @supported For Modern Browers and IE9+
       */
       
      _that.element.addEventListener(type, callback, false);
       
    } else if(_that.element.attachEvent){
       
      /**
       * @supported For IE5+
       */
 
      // 自定义事件处理
      if( type.indexOf('custom') != -1 ){
 
        if( isNaN( _that.element[type] ) ){
 
          _that.element[type] = 0;
 
        } 
 
        var fnEv = function(event){
 
          event = event ? event : window.event
           
          if( event.propertyName == type ){
            callback.call(_that.element);
          }
        };
 
        _that.element.attachEvent('onpropertychange', fnEv);
 
        // 在元素上存储绑定的 propertychange 的回调,方便移除事件绑定
        if( !_that.element['callback' + callback] ){
     
          _that.element['callback' + callback] = fnEv;
 
        }
    
      // 标准事件处理
      } else {
    
        _that.element.attachEvent('on' + type, callback);
      }
       
    } else {
       
      /**
       * @supported For Others
       */
       
      _that.element['on' + type] = callback;
 
    }
 
    return _that;
  },
 
  /**
   * 移除事件监听
   * 
   * @param {String} type 监听的事件类型
   * @param {Function} callback 回调函数
   */
   
  remove: function(type, callback){
 
    var _that = this;
     
    if(_that.element.removeEventListener){
       
      /**
       * @supported For Modern Browers and IE9+
       */
       
      _that.element.removeEventListener(type, callback, false);
       
    } else if(_that.element.detachEvent){
       
      /**
       * @supported For IE5+
       */
       
      // 自定义事件处理
      if( type.indexOf('custom') != -1 ){
 
        // 移除对相应的自定义属性的监听
        _that.element.detachEvent('onpropertychange', _that.element['callback' + callback]);
 
        // 删除储存在 DOM 上的自定义事件的回调
        _that.element['callback' + callback] = null;
      
      // 标准事件的处理
      } else {
      
        _that.element.detachEvent('on' + type, callback);
      
      }
 
    } else {
       
      /**
       * @supported For Others
       */
       
      _that.element['on' + type] = null;
       
    }
 
    return _that;
 
  },
   
  /**
   * 模拟触发事件
   * @param {String} type 模拟触发事件的事件类型
   * @return {Object} 返回当前的 Kjs 对象
   */
   
  trigger: function(type){
 
    var _that = this;
     
    try {
        // 现代浏览器
      if(_that.element.dispatchEvent){
        // 创建事件
        var evt = document.createEvent('Event');
        // 定义事件的类型
        evt.initEvent(type, true, true);
        // 触发事件
        _that.element.dispatchEvent(evt);
      // IE
      } else if(_that.element.fireEvent){
         
        if( type.indexOf('custom') != -1 ){
 
          _that.element[type]++;
 
        } else {
 
          _that.element.fireEvent('on' + type);
        }
    
      }
 
    } catch(e){
 
    };
 
    return _that;
       
  }
}
 
Ev.fn.init.prototype = Ev.fn;
 
})( window );
测试用例1(自定义事件测试)

// 测试用例1(自定义事件测试)
// 引入事件机制
// ...
// 捕捉 DOM
var testBox = document.getElementById('testbox');
// 回调函数1
function triggerEvent(){
    console.log('触发了一次自定义事件 customConsole');
}
// 回调函数2
function triggerAgain(){
    console.log('再一次触发了自定义事件 customConsole');
}
// 封装
testBox = $(testBox);
// 同时绑定两个回调函数,支持链式调用
testBox.add('customConsole', triggerEvent).add('customConsole', triggerAgain);

完整的代码在 Demo。

打开 Demo 后,在 console 中调用 testBox.trigger('customConsole') 自行触发自定义事件,可以看到 console 输出两个提示语,再输入 testBox.remove('customConsole', triggerAgain) 移除对后一个监听,这时再使用 testBox.trigger('customConsole') 触发自定义事件,可以看到 console 只输出一个提示语,即成功移除后一个监听,至此事件机制所有功能正常工作。

Javascript 相关文章推荐
Node.js中使用计时器定时执行函数详解
Aug 15 Javascript
javascript创建动态表单的方法
Jul 25 Javascript
jquery不常用方法汇总
Jul 26 Javascript
javascript特效实现——当前时间和倒计时效果的简单实例
Jul 20 Javascript
jQuery下拉菜单的实现代码
Nov 03 Javascript
ajax 提交数据到后台jsp页面及页面跳转问题
Jan 19 Javascript
微信小程序-横向滑动scroll-view隐藏滚动条
Apr 20 Javascript
ES6入门教程之Iterator与for...of循环详解
May 17 Javascript
深入浅析Vue中的 computed 和 watch
Jun 06 Javascript
JS实现显示当前日期的实例代码
Jul 03 Javascript
微信小程序身份证验证方法实现详解
Jun 28 Javascript
js 获取扫码枪输入数据的方法
Jun 10 Javascript
BootstrapTable与KnockoutJS相结合实现增删改查功能【一】
May 10 #Javascript
JS组件系列之Bootstrap table表格组件神器【二、父子表和行列调序】
May 10 #Javascript
解决jquery无法找到其他父级子集问题的方法
May 10 #Javascript
JS组件系列之Bootstrap table表格组件神器【终结篇】
May 10 #Javascript
Bootstrap Fileinput文件上传组件用法详解
May 10 #Javascript
javascript实现的猜数小游戏完整实例代码
May 10 #Javascript
BootStrap文件上传样式超好看【持续更新】
May 10 #Javascript
You might like
Admin generator, filters and I18n
2011/10/06 PHP
php第一次无法获取cookie问题处理
2014/12/15 PHP
Yii框架结合sphinx,Ajax实现搜索分页功能示例
2016/10/18 PHP
thinkphp框架page类与bootstrap分页(美化)
2017/06/25 PHP
PHP根据树的前序遍历和中序遍历构造树并输出后序遍历的方法
2017/11/10 PHP
jQuery编写widget的一些技巧分享
2010/10/28 Javascript
基于jQuery的一个扩展form序列化到json对象
2010/12/09 Javascript
使用jQuery中的when实现多个AJAX请求对应单个回调的例子分享
2014/04/23 Javascript
javascript获得当前的信息的一些常用命令
2015/02/25 Javascript
基于jQuery+PHP+Mysql实现在线拍照和在线浏览照片
2015/09/06 Javascript
前端框架Vue.js中Directive知识详解
2016/09/12 Javascript
bootstrap——bootstrapTable实现隐藏列的示例
2017/01/14 Javascript
详谈Angular 2+ 的表单(一)之模板驱动型表单
2017/04/25 Javascript
强大的 Angular 表单验证功能详细介绍
2017/05/23 Javascript
深入理解JS中Number(),parseInt(),parseFloat()三者比较
2018/08/24 Javascript
浅谈React碰到v-if
2018/11/04 Javascript
vue 项目接口管理的实现
2019/01/17 Javascript
微信小程序和H5页面间相互跳转代码实例
2019/09/19 Javascript
React 条件渲染最佳实践小结(7种)
2020/09/27 Javascript
实现vuex原理的示例
2020/10/21 Javascript
[01:19:34]2014 DOTA2国际邀请赛中国区预选赛 New Element VS Dream time
2014/05/22 DOTA
Python使用Supervisor来管理进程的方法
2015/05/28 Python
Python序列循环移位的3种方法推荐
2018/04/09 Python
在python中安装basemap的教程
2018/09/20 Python
Python中is和==的区别详解
2018/11/15 Python
python+PyQT实现系统桌面时钟
2020/06/16 Python
python用TensorFlow做图像识别的实现
2020/04/21 Python
python爬虫线程池案例详解(梨视频短视频爬取)
2021/02/20 Python
日本非常有名的内衣丝袜品牌:GUNZE
2017/01/06 全球购物
桥梁与隧道工程专业本科生求职信
2013/10/08 职场文书
工业学校毕业生自荐书
2014/01/03 职场文书
关于环保的演讲稿
2014/05/10 职场文书
班组长安全工作职责
2014/07/15 职场文书
2014年国庆节活动总结
2014/08/26 职场文书
自我检讨书范文
2015/01/28 职场文书
用几道面试题来看JavaScript执行机制
2021/04/30 Javascript