再看这篇博客之前,希望你已经对js高级程序编程一书中的事件模块进行了详读,不然我只能呵呵了。

document.createEventObject,在IE下创建事件对象event。

elem.fireEvent,在IE下触发事件,里面有两个参数type,event。其中type是触发的事件类型,event是事件本身。举个例子:

document.attachEvent('ondataavailable', function (event) {   //document上绑定自定义事件dataavailable
  alert(event.eventType);
});

var event = document.createEventObject();  //调用document对象的createEventObject方法得到一个event的对象实例。
event.eventType = 'message';
document.fireEvent('ondataavailable', event);   //触发document上绑定的自定义事件dataavailable

这时就会执行function,并把event对象实例传入方法中,打印出message。

以上是IE操作的方式,W3C的方式:

document.addEventListener('dataavailable', function (event) {   //document上绑定自定义事件dataavailable
  alert(event.eventType);
}, false);

var event = document.createEvent('HTMLEvents');  //调用document对象的 createEvent 方法得到一个event的对象实例。
event.initEvent("dataavailable", true, true);

// initEvent接受3个参数:
// 事件类型,是否冒泡,是否阻止浏览器的默认行为
event.eventType = 'message';
document.dispatchEvent(event);   //触发document上绑定的自定义事件ondataavailable

W3C要用三个方法:document.createEvent();接受一个参数,创建的事件类型,理应是Events,但是标准浏览器支持HTMLEvents。event.initEvent();接收三个参数,第一个是事件类型,第二个是否冒泡,第三个是否阻止浏览器的默认行为。elem.dispatchEvent(),接收一个参数,创建的event的对象实例。

兼容性方法的写法:

function fireEvent(elem , type , args ){   //第一个参数代表要触发的事件绑定的元素,第二个参数代表要触发的事件类型,第三个是需要传入event对象实例的属性值

  args = args || {};     //比如传入{name:"chaojidan",eventName:"zidingyi"}

  var event;
  if(elem.dispatchEvent){     event = document.createEvent("HTMLEvents");
    event.initEvent(type,true,true);
  }else{     event = document.createEventObject();   }   for(var i in args){     event[i] = args[i];
  }   if(elem.dispatchEvent){     elem.dispatchEvent(event);
  }else{     elem.fireEvent("on"+type, event);
  } }

onXXX绑定方式的缺陷:

(1)对DOM3新增事件或火狐某些私有实现无法支持,比如,DOMContentLoaded事件,DOMMouseScroll事件(此事件用于火狐模拟其他浏览器的mousewheel事件,火狐没有此事件)。

(2)onXXX只允许元素每次绑定一个回调,重复绑定,会覆盖。

(3)在IE下,回调方法没有参数(需要通过window.event),其他浏览器下回调的第一个参数是事件对象。

(4)只能在冒泡阶段可用。

attachEvent绑定方式的缺陷:IE9就开始支持W3C的绑定方式了。

(1)只支持微软系的事件,DOM3的事件不能用,比如:DOMContentLoaded事件

(2)回调中的this,不是指向被绑定元素,而是window。

(3)绑定多个回调方法时,触发时,并不是按照绑定时的顺序依次触发。

(4)只支持冒泡阶段。

addEventListener绑定方式的缺陷:

(1)火狐不支持focusin,focus,DOMFocusIn,DOMFocusOut事件,而且直到现在都不愿意用mousewheel代替DOMMouseScroll。Chrome不支持mouseenter和mouseleave。因此标准浏览器虽然支持这种方式绑定事件,但是支持的事件类型不一样。国内一些浏览器套用webkit内核,为了使自己的浏览器跑分高,竟然实现了一些无用 的空接口来骗过特征侦测,因此有时需要使用功能侦测来检测浏览器是否支持此事件。

(2)此方法还有第四,第五个参数。第四个参数是火狐专有实现,允许跨文档监听事件。第5个参数只存在flash语言的同名方法中。在Flash下,addEventListener的第四个参数用于设置该回调执行时的顺序,数字大的优先执行,第5个参数用于指定对侦听器函数的引用是弱引用还是正常引用。(知道就行,不需要深究)

(3)事件对象成员的不稳定。比如:safari下,event.target可能返回文本节点。event.defaultPrevented代表事件对象有没有调用preventDefault方法。这里有很多奇葩的成员属性,不需要深究。

最后讲一下Dean Edward的addEvent.js源码分析,这是jQuery事件系统的源头。早期的一个事件系统。

function addEvent(element, type, handler){   //元素element,事件类型type,绑定事件处理方法handler。给元素element绑定type的事件类型,事件处理方法是handler

  if(!handler.$$guid) {   //判断处理方法handler是否有$$guid属性,没有就进入if语句

    handler.$$guid = addEvent.guid ++ ;      //addEvent.guid =1;每次绑定一个新的事件处理方法,都会加1,一个唯一的值。

  }

  if(!element.events){   //如果元素没有events属性,就进入if语句

    element.events = {};

  }

  var handlers = element.events[type];    //给element.events对象添加事件类型type的属性

  if(!handlers){     //如果没有element.events[type]不存在,就进入if语句,第一次执行时,是undefined,所以进入if语句

    handlers = element.events[type] = {};

    if(element["on"+type]){    //如果元素之前用onXXX方式绑定过此type事件,就进入if语句

      handlers[0] = element["on"+type];        //把用onXXX绑定的事件处理函数fn赋给element.events[type][0],其实就是element.events[type] ={0:fn}

    }      

  }

  handlers[handler.$$guid] = handler;    //把事件处理方法handler添加到handler对象中,其实就是element.events[type] = {1: handler},如果此元素element之前通过onXXX的方式绑定过fn,那么这时应该是element.events[type] = {0:fn , 1: handler}

  element["on"+type] = handleEvent;  //给元素element绑定type类型的事件,只要在元素element触发了type类型的事件,就会调用handleEvent方法,此方法就会调用element.events[type] 对象中的属性方法。

}

function handleEvent(event){

  event = event || window.event;   //IE浏览器,通过onXXX绑定的事件处理函数,接收不到event对象,只能通过window.event取到。

  var handlers = this.events[event.type]; //this指的是element(事件绑定的元素),event.type是事件触发的类型type。其实就是上面的element.events[type]对象

  for(var i in handlers){

    this.$$handleEvent = handlers[i];    //取到element绑定type事件的事件处理函数,其实就是上面的fn和handler方法赋给element的$$handleEvent属性

    this.$$handleEvent(event) ;    //按照绑定事件处理函数的顺序,执行。先执行fn,在执行handler。并传入已做了兼容性的event对象

  }

}

以上有一个bug,就是event的取值,如果是在iframe中点击事件时,传进来的window就是iframe的window了。需要改成如下格式:

event = event || ((this.ownerDocument || this.document || this).parentWindow || window).event;

this指的是触发事件的元素,如果元素的document有parentWindow属性,那么就证明是在iframe中触发的事件,因此需要取它的父窗口。触发事件的元素可能是document本身,或者是window本身,因此加了后面两个对象this.document(window)和this(document)。因为只有document有parentWindow属性。如果是在最外层window触发的,它的document.parentWindow会返回false,因此直接用window.event。

以上的addEvent方法,在交错引用时,会产出内存泄露(js对象引用DOM元素节点,同时DOM元素节点也引用js对象),因此,建议给元素就分配一个UUID,所有的回调都放到一个js对象存储(不再放在element的属性对象中,这会导致DOM元素节点引用js对象)。代码如下:

addEvent.handlers = {}

function addEvent(element, type, handler){   //元素element,事件类型type,绑定事件处理方法handler。给元素element绑定type的事件类型,事件处理方法是handler

  if(!handler.$$guid) {   //判断处理方法handler是否有$$guid属性,没有就进入if语句

    handler.$$guid = addEvent.guid ++ ;      //addEvent.guid =1;每次绑定一个新的事件处理方法,都会加1,一个唯一的值。

  }    

  if(!element.$$guid) {   //判断元素element是否有$$guid属性,没有就进入if语句

    element.$$guid = addEvent.guid ++ ;      //这里element.$$guid=2,一个唯一的值,如果之前此元素已经绑定过事件,它就不会进入if语句时。

  }

  if(!addEvent.handlers[element.$$guid]){//addEvent.handlers是一个对象,它判断此对象里面是否有此元素对应的唯一的值的属性,其实就是:addEvent.handlers[2]

    addEvent.handlers[element.$$guid] = {};    //如果没有,就进入if语句,addEvent.handlers[2] = {};

  }

  var handlers = addEvent.handlers[element.$$guid][type];    //addEvent.handlers[2][type],第一次绑定时,此type属性值不存在。

  if(!handlers){     //如果没有此type属性值,就进入if语句,第一次执行时,是undefined,所以进入if语句

    handlers = addEvent.handlers[element.$$guid][type] = {};

    if(element["on"+type]){    //如果元素之前用onXXX方式绑定过此type事件,就进入if语句

      handlers[0] = element["on"+type];        //把用onXXX绑定的事件处理函数fn赋给addEvent.handlers[element.$$guid][type] = {0:fn}

    }      

  }

  handlers[handler.$$guid] = handler; //其实就是addEvent.handlers[element.$$guid][type]= {1: handler},如果此元素element之前通过onXXX的方式绑定过fn,那么这时应该是addEvent.handlers[element.$$guid][type] = {0:fn , 1: handler}

  element["on"+type] = handleEvent;

}

以上代码的改进,其实就是给元素element分配了一个唯一的值(UUID),同时把所有的回调方法,都放到addEvent.handlers对象中进行处理了,而不是放到元素element的属性对象中。

jQuery的事件系统就是通过改进上面的代码实现的,它其中绑定事件不使用onXXX的模式(会存在内存泄露),而是使用addEventListener和attachEvent绑定事件。每个元素只绑定一次就OK了,减少了DOM操作。

加油!

第二十二课:js事件原理以及addEvent.js的详解的更多相关文章

  1. NeHe OpenGL教程 第二十二课:凹凸映射

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  2. 风炫安全web安全学习第二十八节课 CSRF攻击原理

    风炫安全web安全学习第二十八节课 CSRF攻击原理 CSRF 简介 跨站请求伪造 (Cross-Site Request Forgery, CSRF),也被称为 One Click Attack 或 ...

  3. “全栈2019”Java第九十二章:外部类与内部类成员覆盖详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  4. 2017.2.13 开涛shiro教程-第十二章-与Spring集成(一)配置文件详解

    原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398 根据下载的pdf学习. 第十二章-与Spring集成(一)配置文件详解 1.pom.xml ...

  5. 从壹开始微服务 [ DDD ] 之十二 ║ 核心篇【下】:事件驱动EDA 详解

    缘起 哈喽大家好,又是周二了,时间很快,我的第二个系列DDD领域驱动设计讲解已经接近尾声了,除了今天的时间驱动EDA(也有可能是两篇),然后就是下一篇的事件回溯,就剩下最后的权限验证了,然后就完结了, ...

  6. 第二十九节,目标检测算法之R-CNN算法详解

    Girshick, Ross, et al. “Rich feature hierarchies for accurate object detection and semantic segmenta ...

  7. Jmeter(二十二) - 从入门到精通 - JMeter断言 - 下篇(详解教程)

    1.简介 断言组件用来对服务器的响应数据做验证,常用的断言是响应断言,其支持正则表达式.虽然我们的通过响应断言能够完成绝大多数的结果验证工作,但是JMeter还是为我们提供了适合多个场景的断言元件,辅 ...

  8. (转)学习淘淘商城第二十二课(KindEditor富文本编辑器的使用)

    http://blog.csdn.net/u012453843/article/details/70184155 上节课我们一起学习了怎样解决KindEditor富文本编辑器上传图片的浏览器兼容性问题 ...

  9. 潭州课堂25班:Ph201805201 django 项目 第二十二课 文章主页 新闻列表页面滚动加载,轮播图后台实现 (课堂笔记)

    新建static/js/news/index.js文件 ,主要用于向后台发送请求, // 新建static/js/news/index.js文件 $(function () { // 新闻列表功能 l ...

随机推荐

  1. poj 2391 Ombrophobic Bovines(最大流+floyd+二分)

    Ombrophobic Bovines Time Limit: 1000MSMemory Limit: 65536K Total Submissions: 14519Accepted: 3170 De ...

  2. The Monocycle(BFS)

    The Monocycle Time Limit: 3000MS64bit IO Format: %lld & %llu [Submit]   [Go Back]   [Status] Des ...

  3. Linux shell basic2 cat find tr

    Cat stands for concatenate. Case 1. When the text files have more blank lines, we want to remove the ...

  4. openPOWERLINK开源POWERLINK协议栈说明文档中文非官方翻译

    GitBook分享,翻译进行中:https://winshton.gitbooks.io/openpowerlink-stack-cn/content/

  5. 【MVC 4】5.SportsSore —— 一个真实的应用程序

     作者:[美]Adam Freeman      来源:<精通ASP.NET MVC 4> 前面建立的都是简单的MVC程序,现在到了吧所有事情综合在一起,以建立一个简单但真实的电子商务应用 ...

  6. HDU 1896 Stones --优先队列+搜索

    一直向前搜..做法有点像模拟.但是要用到出队入队,有点像搜索. 代码: #include <iostream> #include <cstdio> #include <c ...

  7. HDU 3600 Simple Puzzle 归并排序 N*N数码问题

    先介绍八数码问题: 我们首先从经典的八数码问题入手,即对于八数码问题的任意一个排列是否有解?有解的条件是什么? 我在网上搜了半天,找到一个十分简洁的结论.八数码问题原始状态如下: 1 2 3 4 5 ...

  8. TestLink学习一:Windows搭建Apache+MySQL+PHP环境

    PHP集成开发环境有很多,如XAMPP.AppServ......只要一键安装就把PHP环境给搭建好了.但这种安装方式不够灵活,软件的自由组合不方便,同时也不利于学习.所以我还是喜欢手工搭建PHP开发 ...

  9. 【mybatis】1、入门CURD基本操作(环境搭建)

    #1.基本环境 环境 版本 jdk 1.7.0_10 ide eclipse-jee-luna-SR2-win32-x86_64 maven 3.3.3 mybatis 3.2.7 mysql 5.1 ...

  10. java 14 -5 System类

    System类包含一些有用的类字段和方法.它不能被实例化. 方法: 1.public static void gc():运行垃圾回收器. 2.public static void exit(int s ...