javascript事件机制学习(一)——个人实现

 起因:在面试的时候被问到在不提供任何DOM API,不能使用jQuery,如何实现事件的监听和触发。

 需求:需要在全局范围监听事件。为了方便事件类型的扩展,提供注册事件,移除事件的方法。对于某一类的时间,提供监听事件,触发事件,和移除事件三种方法。因此针对需求,设计以下的数据结构:

 结构:用一个Object存储全部的事件,其中包括三个(或多个)handler,为addHandler,removeHandler和setupHandler。分别用于在第一次注册注册事件的时候,监听该类型的事件时,以及移除对改事件的监听时执行回调。EventQueue用于存储,监听该时间的全部事件对象。on用于将事件对象添加到响应的队列里面,off用于将事件对象从监听队列里移除,fire用于触发这一类事件。依次执行队列里面的时间。

代码分析:

整体数据结构:

 var EventManager = function(){
   	  this.eventMap = {};
   	  this.handlers = ["add","remove","setup"];
   	  this.guid = 0;
   };

注册事件:

EventManager.prototype.registerEvent = function(name,eventObj) {
   	 var eventMap = this.eventMap;

   	 if(!eventMap.hasOwnProperty(name)){
   	 	eventMap[name] = {};

   	 	for(var i=0,len=this.handlers.length; i<len; i++){
   	 		var handlerName = this.handlers[i] + "Handler";
   	 		eventMap[name][handlerName] = eventObj[handlerName] || null;
   	 	}

   	 	eventMap[name]["queue"] = [];
   	 }else{
   	 	for(var key in eventObj){
   	 	    if( !key.test(/'queue'/g)){
   	 	      eventMap[name][key] = eventObj[key];
   	 	    }else{
   	 	    	eventMap[name][key].concat(eventObj[key].slice(0));
   	 	    }
   	 		
   	 	}
   	 }

   };

注册事件分为两种情况,一种是该事件已被注册的情况。这种情况,把提供的eventObj的时间队列添加到原有的队列后面。如果还没有注册该事件,则根据eventObj中提供的handler,创建新的监听对象。


移除事件类型:

EventManager.prototype.removeEvent = function(name){
   	  var eventMap = this.eventMap;

   	  if(!eventMap.hasOwnProperty(name)){console.log("has no such event type"); return ;}

   	  delete eventMap[name];
   }

先检查存不存在该类型的事件,存在的话,则从eventMap中delete掉。

监听事件:

EventManager.prototype.on = function(elem,type,callback){
   	  var args = Array.prototype.slice.call(arguments, 0);
   	  if(args.length < 3 || !this.eventMap.hasOwnProperty(type)) return ; // 如果参数提供的不齐全,或者没有该type,那么直接返回

      //这里应该调整一下参数,先不实现
      var queue = this.eventMap[type]["queue"],
          addHandler = this.eventMap[type]["addHandler"],
          setupHandler = this.eventMap[type]["setupHandler"],
          i = 0, len = queue.length,dom = "dom";

      
      for(; i<len; i++){
      	if(elem.nodeType == 1 && queue[i][dom] === elem){//该元素已经监听过的情况
           addHandler &&addHandler.apply(elem);

           queue[i]["callbacks"].push(callback);
           break;
      	}
      }

      if(i == len){ // 该元素还没有监听任何事件
      	var temp = {
      		"id" : this.guid++,
      		"dom" : elem,
      		"callbacks" : []
      	};

      	setupHandler && setupHandler.apply(elem);
      	temp["callbacks"].push(callback);

      	queue.push(deepClone(temp));
      	temp = null;
      }
   }

首先需要确保监听类型,对象和回调都提供。并且要判断监听的事件类型是否存在。如果不满足条件,直接返回。

然后需要判断,该elem是否已经监听过这个事件了。如果有,则把callback放到原来的回调队列里面。如果还没有监听的话,那么产生一个{},其中包括id,执行的对象elem和回调队列。将该obj放到监听事件的队列里面。


触发事件:

EventManager.prototype.fire = function(elem,type){
     var queue = this.eventMap[type]["queue"];

     if(arguments.length < 2 || queue === undefined) return;

     var i=0,len = queue.length;
     for(; i<len; i++){
        var context = queue[i]["dom"],
            callbacks = queue[i]["callbacks"];

        callbacks.forEach(function(callback){
           callback.call(context,elem);
        });
     }
   }
首先也要确保传参正确,并且存在该事件类型。如果没有,则直接返回。 然后依次取出queue里面的监听对象。获取执行回调的context和回调们。依次执行


移除事件:

EventManager.prototype.off = function(elem,type){
   	  var queue = this.eventMap[type]["queue"];

   	  if(arguments.length < 2 || queue == undefined) return ;

   	  var i=0, len= queue.length,removeHandler = this.eventMap[type]["removeHandler"];

   	  for(; i<len; i++){
   	  	 if(elem.nodeType == 1 && queue[i]["dom"] === elem){
   	  	 	removeHandler && removeHandler.apply(elem);
   	  	 	queue.splice(i,1);
   	  	 	break;
   	  	 }
   	  }
   }

找到该元素在该类型中的事件对象,然后从queue中移除。并且执行removeHandler


完整代码:

https://github.com/lizzyDeng/personalLib/blob/master/EventManager.js

猜你喜欢

转载自blog.csdn.net/u013237862/article/details/65937343