详解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 相关文章推荐
javascript 树形导航菜单实例代码
Aug 13 Javascript
调用jQuery滑出效果时闪烁的解决方法
Mar 27 Javascript
jQuery获得字体颜色16位码的方法
Feb 20 Javascript
JavaScript从数组的indexOf()深入之Object的Property机制
May 11 Javascript
JavaScript学习笔记整理_setTimeout的应用
Sep 19 Javascript
Angular.JS中指令ng-if、ng-show/ng-hide和ng-switch的使用教程
May 07 Javascript
angular学习之从零搭建一个angular4.0项目
Jul 10 Javascript
元素全屏的设置与监听实例
Nov 28 Javascript
深入浅出webpack之externals的使用
Dec 04 Javascript
js图片查看器插件用法示例
Jun 22 Javascript
react实现antd线上主题动态切换功能
Aug 12 Javascript
layui实现数据表格自定义数据项
Oct 26 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
PHP 错误之引号中使用变量
2009/05/04 PHP
[原创]PHP获取数组表示的路径方法分析【数组转字符串】
2017/09/01 PHP
PHP两个n位的二进制整数相加问题的解决
2018/08/26 PHP
JavaScript中switch判断容易犯错的一个细节
2014/08/27 Javascript
AngularJs Dependency Injection(DI,依赖注入)
2016/09/02 Javascript
基于javascript实现的快速排序
2016/12/02 Javascript
Bootstrap进度条学习使用
2017/02/09 Javascript
vue的Virtual Dom实现snabbdom解密
2017/05/03 Javascript
jquery处理checkbox(复选框)是否被选中实例代码
2017/06/12 jQuery
一个有意思的鼠标点击文字特效jquery代码
2017/09/23 jQuery
简单说说angular.json文件的使用
2018/10/29 Javascript
利用Vue-draggable组件实现Vue项目中表格内容的拖拽排序
2019/06/07 Javascript
three.js 如何制作魔方
2020/07/31 Javascript
vue element ui validate 主动触发错误提示操作
2020/09/21 Javascript
python读取json文件并将数据插入到mongodb的方法
2015/03/23 Python
Python利用正则表达式匹配并截取指定子串及去重的方法
2015/07/30 Python
Python简单计算文件MD5值的方法示例
2018/04/11 Python
浅述python中深浅拷贝原理
2018/09/18 Python
Pandas GroupBy对象 索引与迭代方法
2018/11/16 Python
Django之Mode的外键自关联和引用未定义的Model方法
2018/12/15 Python
Python 生成一个从0到n个数字的列表4种方法小结
2019/11/28 Python
python实现12306登录并保存cookie的方法示例
2019/12/17 Python
python中线程和进程有何区别
2020/06/17 Python
英国领先的狗和宠物美容专家:Christies Direct
2017/04/03 全球购物
伦敦一家西班牙童装精品店:La Coqueta
2018/02/02 全球购物
应届毕业生应聘自荐信范文
2014/02/26 职场文书
普通话宣传标语
2014/06/26 职场文书
正风肃纪查摆剖析材料
2014/10/10 职场文书
2015年大学班长个人工作总结
2015/04/24 职场文书
安全温馨提示语大全
2015/07/14 职场文书
环境保护宣传标语大全!
2019/06/28 职场文书
2019年汽车租赁合同范本!
2019/08/12 职场文书
python基于tkinter制作无损音乐下载工具
2021/03/29 Python
Python爬虫进阶之Beautiful Soup库详解
2021/04/29 Python
Pytorch使用shuffle打乱数据的操作
2021/05/20 Python
浅谈python数据类型及其操作
2021/05/25 Python