再看这篇博客之前,希望你已经对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. chrome升级54以后,显示Adobe Flash Player 因过期而遭到阻止

    请直接下载 最新的Adobe flash player 离线安装包.经测试,在线安装不管用. 百度云地址: install_flash_player_23_ppapi.exe  密码:8c2i

  2. [转]Android输出Log到文件

    前言:开发中遇到mx4这款机型Eclipse联调不上,logcat看不了,需要输出生成文件查看调试信息.网上搜了下,功能很完善了.startService和过滤输出信息需要自己添加设置,另外注意添加权 ...

  3. Python中的库使用之一 PIL

    先上代码:本文主要工给自己参考,在需要的时候直接搜索查找就行了,不想看没有实际运行例子的文档,当参考完这部分还哦未能解决问题在参考PIL的相关文档! Skip to content This repo ...

  4. SSAS CUBE TEST CASES

    经过周末两天和今天的努力,基本上完成并修复了一些bug并且集成到我的MSBIHelper项目中去,可以进行数据测试了.效果图如下: 可以帮助开发人员快速生成等值的Tsql和mdx查询,辅助测试人员快速 ...

  5. runc start container流程分析

    1.runc/start.go Action: func(context *cli.Context) error 该函数首先调用container, err := getContainer(conte ...

  6. 如何用ZBrush雕刻出栩栩如生的头发(二)

     之前的ZBrush教程教大家使用SubTool为模型添加了杂乱头发效果的两种雕刻手法,今天将继续介绍其他方法对模型头发雕刻技巧和细节进行讲解.文章内容仅以fisker老师讲述为例,您也可以按照自己的 ...

  7. 利用Google Speech API实现Speech To Text

    很久很久以前, 网上流传着一个免费的,识别率暴高的,稳定的 Speech To Text API, 那就是Google Speech API. 但是最近再使用的时候,总是返回500 Error. 后来 ...

  8. Django数据库怎么给字段设置主键

    id = models.IntegerField(primary_key = True) 附: null :缺省设置为false.通常不将其用于字符型字段上,比如CharField,TextField ...

  9. HDU 4941 Magical Forest --STL Map应用

    题意: 有n*m个格子(n,m <= 2*10^9),有k(k<=10^5)个格子中有值,现在有三种操作,第一种为交换两行,第二种为交换两列,交换时只有两行或两列都有格子有值或都没有格子有 ...

  10. POJ 2142 The Balance【扩展欧几里德】

    题意:有两种类型的砝码,每种的砝码质量a和b给你,现在要求称出质量为c的物品,要求a的数量x和b的数量y最小,以及x+y的值最小. 用扩展欧几里德求ax+by=c,求出ax+by=1的一组通解,求出当 ...