JavaScript 自定义事件实现:对象直接量与原型链两种封装方案

作者:闭包旅人时间:2026-10-29 20:38:09浏览:451

本文详细解析 JavaScript 中自定义事件的两种封装实现方式。首先介绍基于对象直接量的方法,通过 handlers 映射表存储事件类型与处理函数的数组关系,支持多监听器注册与触发,但存在属性覆盖风险。随后探讨基于对象原型链的封装方案,利用 prototype 提升安全性与复用性。文章包含完整的代码示例、调用方法及优缺点对比,帮助开发者理解并应用自定义事件机制。

JavaScript 自定义事件概述

在 DOM 操作过程中,开发者经常使用 onclick、onmouseover 等浏览器内置的特定行为事件。所谓自定义事件,即由开发者自行定义事件类型及对应的处理函数,并在需要时调用相应的处理程序。

浏览器默认事件回顾

1. JavaScript 支持的浏览器默认事件

浏览器特定行为事件、系统事件或 JS 默认事件,这些概念指代相同内容。关于 JS 默认事件的事件绑定与移除操作,开发者通常已熟悉,例如以下示例:


//DOM0级事件处理程序
var oDiv = document.getElementById('oDiv');
oDiv.onclick = function(){
  alert("你点击了我");
}

或者采用另一种方式:


//DOM2级事件处理程序
var oDiv = document.getElementById('oDiv');

//非ie
oDiv.addEventListener("click",function(){
  alert("你点击了我");
},false); 

//ie
oDiv.attachEvent("onclick", function(){
  alert("你点击了我");
});

此处不再对默认事件进行深入探讨,因为本文重点在于 JS 自定义事件。以下提供一段之前封装的用于处理 JS 默认事件的代码供参考:


//跨浏览器的事件处理程序 
//调用时候直接用domEvent.addEvent( , , );直接调用 
//使用时候,先用addEvent添加事件,然后在handleFun里面直接写其他函数方法,如getEvent; 
//addEventListener和attachEvent---都是dom2级事件处理程序 
var domEvent = { 
  //element:dom对象,event:待处理的事件,handleFun:处理函数 
  //事件名称,不含“on”,比如“click”、“mouseover”、“keydown”等 
  addEvent:function(element,event,handleFun){ 
    //addEventListener----应用于mozilla 
    if(element.addEventListener){ 
      element.addEventListener(event,handleFun,false); 
    }//attachEvent----应用于IE 
    else if(element.attachEvent){ 
      element.attachEvent("on"+event,handleFun); 
    }//其他的选择dom0级事件处理程序 
    else{ 
      //element.onclick===element["on"+event]; 
      element["on"+event] = handleFun; 
    } 
  }, 
  //事件名称,含“on”,比如“onclick”、“onmouseover”、“onkeydown”等 
  removeEvent:function(element,event,handleFun){ 
    //removeEventListener----应用于mozilla 
    if (element.removeEventListener) { 
      element.removeEventListener(event,handleFun,false); 
    }//detachEvent----应用于IE 
    else if (element.detachEvent) { 
      element.detachEvent("on"+event,handleFun); 
    }//其他的选择dom0级事件处理程序 
    else { 
      element["on"+event] = null; 
    } 
  }, 
  //阻止事件冒泡 
  stopPropagation:function(event){ 
    if(event.stopPropagation){ 
      event.stopPropagation(); 
    }else{ 
      event.cancelBubble = true;//IE阻止事件冒泡,true代表阻止 
    } 
  }, 
  //阻止事件默认行为 
  preventDefault:function(event){ 
    if(event.preventDefault){ 
      event.preventDefault(); 
    }else{ 
      event.returnValue = false;//IE阻止事件冒泡,false代表阻止 
    } 
  }, 
  //获得事件元素 
  //event.target--非IE 
  //event.srcElement--IE 
  getElement:function(event){ 
    return event.target || event.srcElement; 
  }, 
  //获得事件 
  getEvent:function(event){ 
    return event? event : window.event; 
  }, 
  //获得事件类型 
  getType:function(event){ 
    return event.type; 
  } 
}; 

接下来进入正题,探讨 JS 自定义事件的实现。

对象直接量封装方案

2. 对象直接量封装 JS 自定义事件

基于上述封装思路,我们可以进行如下构思:


var eventTarget = {
  addEvent: function(){
    //添加事件
  },
  fireEvent: function(){
    //触发事件
  },
  removeEvent: function(){
    //移除事件
  }
};

相信这种思路易于理解。但随之产生一个问题:JS 默认事件由系统一一对应,而自定义事件的映射关系需由开发者自行建立。具体构建方式如下:


var eventTarget = {
  //保存映射
  handlers:{},
  addEvent: function(){
    //处理代码
  },
  fireEvent: function(){
    //触发代码
  },
  removeEvent: function(){
    //移出代码
  }
};

构建映射关系的代码如下:


handlers = {
  "type1":[
    "fun1",
    "fun2",
    // "..."
  ],
  "type2":[
    "fun1",
    "fun2"
    // "..."
  ]
  //"..."
}

如此设计,每个事件类型可关联多个处理函数,便于后续扩展。接下来是具体的代码实战部分。

思路已清晰,直接附上实现代码:


//直接量处理js自定义事件
var eventTarget = {
  //保存事件类型,处理函数数组映射
  handlers:{},
  //注册给定类型的事件处理程序,
  //type -> 自定义事件类型, handler -> 自定义事件回调函数
  addEvent: function(type, handler){
    //判断事件处理数组是否有该类型事件
    if(eventTarget.handlers[type] == undefined){
      eventTarget.handlers[type] = [];
    }
    //将处理事件push到事件处理数组里面
    eventTarget.handlers[type].push(handler);
  },
  //触发一个事件
  //event -> 为一个js对象,属性中至少包含type属性,
  //因为类型是必须的,其次可以传一些处理函数需要的其他变量参数。(这也是为什么要传js对象的原因)
  fireEvent: function(event){
    //判断是否存在该事件类型
    if(eventTarget.handlers[event.type] instanceof Array){
      var _handler = eventTarget.handlers[event.type];
      //在同一个事件类型下的可能存在多种处理事件,找出本次需要处理的事件
      for(var i = 0; i < _handler.length; i++){
        //执行触发
        _handler[i](event);
      }
    }
  },
  //注销事件
  //type -> 自定义事件类型, handler -> 自定义事件回调函数
  removeEvent: function(type, handler){
    if(eventTarget.handlers[type] instanceof Array){
      var _handler = eventTarget.handlers[type];
      //在同一个事件类型下的可能存在多种处理事件,找出本次需要处理的事件
      for(var i = 0; i < _handler.length; i++){
        //找出本次需要处理的事件下标
        if(_handler[i] == handler){
          break;
        }
      }
      //删除处理事件
      _handler.splice(i, 1);
    }
  }
};

这是一种调用运行的方法:


eventTarget.addEvent("eat",function(){
  console.log(123);  //123
});
eventTarget.fireEvent({type: "eat"});

该方法存在一个缺点:无法删除已注册的处理事件。这是因为使用了映射表存储数据,且不建议在映射表中存入过多数据。

另一种方法是将处理事件提取出来(推荐):


function b(){
   console.log(123);
}
eventTarget.addEvent("eat",b);
eventTarget.fireEvent({
  type: "eat"
});                   //123
eventTarget.removeEvent("eat",b);
eventTarget.fireEvent({type: "eat"});  //空

也可以传递更多参数:


eventTarget.fireEvent({
  type: "eat",
  food: "banana"
}); 
function b(data){
   console.log(data.food); //banana
}

总结:字面量方法存在一定缺陷,若不慎在 handler 函数中将某个属性赋值为 null,可能导致 eventTarget 方法崩溃。相比之下,原型链封装更为安全。

对象原型链封装方案

3. 对象原型封装 JS 自定义事件

由于前文思路已阐述清楚,此处直接附上代码。读者可自行研究其利弊,或寻找更优解决方案:


//自定义事件构造函数
function EventTarget(){
  //事件处理程序数组集合
  this.handlers = {};
}
//自定义事件的原型对象
EventTarget.prototype = {
  //设置原型构造函数链
  constructor: EventTarget,
  //注册给定类型的事件处理程序,
  //type -> 自定义事件类型, handler -> 自定义事件回调函数
  addEvent: function(type, handler){
    //判断事件处理数组是否有该类型事件
    if(typeof this.handlers[type] == 'undefined'){
      this.handlers[type] = [];
    }
    //将处理事件push到事件处理数组里面
    this.handlers[type].push(handler);
  },
  //触发一个事件
  //event -> 为一个js对象,属性中至少包含type属性,
  //因为类型是必须的,其次可以传一些处理函数需要的其他变量参数。(这也是为什么要传js对象的原因)
  fireEvent: function(event){
    //模拟真实事件的event
    if(!event.target){
      event.target = this;
    }
    //判断是否存在该事件类型
    if(this.handlers[event.type] instanceof Array){
      var handlers = this.handlers[event.type];
      //在同一个事件类型下的可能存在多种处理事件,找出本次需要处理的事件
      for(var i = 0; i < handlers.length; i++){
        //执行触发
        handlers[i](event);
      }
    }
  },
  //注销事件
  //type -> 自定义事件类型, handler -> 自定义事件回调函数
  removeEvent: function(type, handler){
    //判断是否存在该事件类型
    if(this.handlers[type] instanceof Array){
      var handlers = this.handlers[type];
      //在同一个事件类型下的可能存在多种处理事件
      for(var i = 0; i < handlers.length; i++){
        //找出本次需要处理的事件下标
        if(handlers[i] == handler){
          break;
        }
      }
      //从事件处理数组里面删除
      handlers.splice(i, 1);
    }
  }
};

调用方法如下:


function b(){
  console.log(123);
}

var target = new EventTarget();
target.addEvent("eat", b);

target.fireEvent({
  type: "eat"
});                 //123

原型链方法与对象直接量方法功能一致。

以上就是本文的全部内容,希望对大家的学习有所帮助。

作者最新文章