1. 前言:

在看Aaron的jquery源码解读时候,看到事件系统那块,作者提到了Dean Edwards的添加事件的设计,于是就点进去看了看。首先让我吃惊的是,代码非常少,寥寥几十行,非常简单。于是我就仔细的看了看(如果代码太多,可能就直接不看了)。

这段代码是Dean Edwards在2005年写的了,那时候还没有jquery。但是它的设计思路确实和jquery的事件系统有些相似,即便是在9年之后的今天。

于是把这段代码仔细研究,并在此跟大家分享以下。它将帮助你更好的理解jquery的事件系统。

先把源码粘上,:

 function addEvent(element, type, handler) {
// assign each event handler a unique ID
if (!handler.$$guid) handler.$$guid = addEvent.guid++;
// create a hash table of event types for the element
if (!element.events) element.events = {};
// create a hash table of event handlers for each element/event pair
var handlers = element.events[type];
if (!handlers) {
handlers = element.events[type] = {};
// store the existing event handler (if there is one)
if (element["on" + type]) {
handlers[0] = element["on" + type];
}
}
// store the event handler in the hash table
handlers[handler.$$guid] = handler;
// assign a global event handler to do all the work
element["on" + type] = handleEvent;
};
// a counter used to create unique IDs
addEvent.guid = 1; function removeEvent(element, type, handler) {
// delete the event handler from the hash table
if (element.events && element.events[type]) {
delete element.events[type][handler.$$guid];
}
}; function handleEvent(event) {
// grab the event object (IE uses a global event object)
event = event || window.event;
// get a reference to the hash table of event handlers
var handlers = this.events[event.type];
// execute each event handler
for (var i in handlers) {
this.$$handleEvent = handlers[i];
this.$$handleEvent(event);
}
};

2. 该设计的优点:

Dean Edwards在文章中提到了该设计的几个优点:

it performs no object detection  不执行对象检测,不理解何意。。。

it does not use the addeventListener/attachEvent methods  不使用addeventListener/attachEvent方法,因为这两个方法分别由不同的浏览器支持,使用时候需要判断。  但是,在Dean Edwards提供的下载代码中,应用到了addeventListener。

it keeps the correct scope (the this keyword)  保持正确的作用域,即this关键字

it passes the event object correctly  正确的传递event对象

it is entirely cross-browser (it will probably work on IE4 and NS4)  保证浏览器兼容性,甚至支持IE4和NetSape4(2005年)

and from what I can tell it does not leak memory  不会出现内存泄漏

3. 代码解读:

3.1 事件添加方法addEvent():

         //事件添加方法
function addEvent(element, type, handler) { // assign each event handler a unique ID
// 为传入的每个事件初始化一个唯一的id
if (!handler.$$guid) handler.$$guid = addEvent.guid++; //下文:addEvent.guid = 1; // create a hash table of event types for the element
// 给element维护一个events属性,初始化为一个空对象。
// element.events的结构类似于 { "click": {...}, "dbclick": {...}, "change": {...} }
// 即element.events是一个对象,其中每个事件类型又会对应一个对象
if (!element.events) element.events = {}; // create a hash table of event handlers for each element/event pair
// 试图取出element.events中当前事件类型type对应的对象,赋值给handlers
var handlers = element.events[type];
if (!handlers) {
handlers = element.events[type] = {};
//如果handlers是undefined,则初始化为空对象 // store the existing event handler (if there is one)
// 如果这个element已经有了一个方法,例如已经有了onclick方法
// 就把element的onclick方法赋值给handlers的0元素,此时handlers的结构就是:
// { 0: function(e){...} }
// 此时element.events的结构就是: { "click": { 0: function(e){...} }, /*省略其他事件类型*/ }
if (element["on" + type]) {
handlers[0] = element["on" + type];
}
}
// store the event handler in the hash table
// 把当前的事件handler存放到handlers中,handler.$$guid = addEvent.guid++; addEvent.guid = 1; 肯定是从1开始累加的
// 因此,这是handlers的结构就是 { 0: function(e){...}, 1: function(){}, 2: function(){} 等等... }
handlers[handler.$$guid] = handler; // assign a global event handler to do all the work
// 下文定义了一个handleEvent(event)函数
// 将这个函数,绑定到element的type事件上。 说明:在element进行click时,将会触发handleEvent函数,handleEvent函数将会查找element.events,并调用相应的函数。可以把handleEvent称为“主监听函数”
element["on" + type] = handleEvent;
}; // a counter used to create unique IDs
addEvent.guid = 1;

以上代码都给出了详细的注释,应该能看明白了。重新梳理以下数据结构,经过addEvent()函数之后,当前的数据结构为:(假如type = 'click')

     element: {
onclick: handleEvent(event), /*下文定义的函数*/
events: {
click:{
0: function(){...}, /*element已有的click事件*/
1: function(){...},
2: function(){...}
/*.......其他事件......*/
},
change:{
/*省略*/
},
dbclick:{
/*省略*/
}
}
}

这样的设计,其实已经具备了jquery事件系统的雏形,包含了两个最主要的特点:

  • element上的所有事件,将保存到element.events属性中,不是直接绑定到element上;
  • handleEvent作为element所有事件的“主监听函数”,有它统一管理element上的所有函数。

接着往下看:

3.2 主监听函数handleEvent(event):

         //主监听函数
function handleEvent(event) {
// grab the event object (IE uses a global event object)
// 在IE中,event需要通过window.event获取
event = event || window.event; // get a reference to the hash table of event handlers
// 根据事件类型在events中获取事件集合(events的数据结构,参考addEvent方法的注释)
var handlers = this.events[event.type];
// 注意!注意! 这里的this不是window,而是element对象,因为上文 element["on" + type] = handleEvent;
// 所以在程序执行时,handleEvent已经作为了element的一个属性,它的作用域是element,即this === element // execute each event handler
// 循环执行handlers集合里的所有函数 另外,这里执行事件时传递的event,无论在什么浏览器下,都是正确的
for (var i in handlers) {
this.$$handleEvent = handlers[i];
this.$$handleEvent(event); //此处为何要把handlers[i]赋值给this.$$handleEvent,然后在执行呢?
//而不是直接执行handlers[i](event)?
//跟内存泄漏有关?
//我也没看明白,大家自己思考的,知道的可以分享给大家。
}
};

以上就是主监听函数的实现过程,都做了注释,也不叫好理解,有个问题,已经在代码中有黄色背景标出来了,有了解的,也麻烦分享给大家。

jquery的主监听函数执行时候实现的比较复杂,但是思路上和这个是一样的。

3.3 移除函数事件:

        //移除函数事件
function removeEvent(element, type, handler) {
// delete the event handler from the hash table
// 循环element.events[type],根据handler的唯一的id,进行delete
if (element.events && element.events[type]) {
delete element.events[type][handler.$$guid];
}
};

移除函数比较简单,不多解释。

4. 总结

这段代码相对于jquery的事件系统,少了事件的代理,以及模拟的时间冒泡。不考虑代理,当然就简单许多。

但是它已经点出了jquery事件系统的原型,理解它,能帮助你更好的理解jquery事件系统。

补充:司徒正美的新书《javascript框架设计》中专门有一节讲解:11.4 Dean Edward的addEvent.js源码分析 260页

js便签笔记(5)——Dean Edwards大牛的跨浏览器AddEvent()设计(不知道是不是jQuery事件系统的原型)的更多相关文章

  1. js便签笔记(12)——浏览TOM大叔博客的学习笔记 part2

    1. 前言 昨天写了<js便签笔记(11)——浏览TOM大叔博客的学习笔记 part1>,简单记录了几个问题.part1的重点还是在于最后那个循环创建函数的问题,也就是多个子函数公用一个闭 ...

  2. js便签笔记(2)——DOM元素的特性(Attribute)和属性(Property)

    1.介绍: 上篇js便签笔记http://www.cnblogs.com/wangfupeng1988/p/3626300.html最后提到了dom元素的Attribute和Property,本文简单 ...

  3. js便签笔记(13)——jsonp其实很简单【ajax跨域请求】

    前两天被问到ajax跨域如何解决,还真被问住了,光知道有个什么jsonp,迷迷糊糊的没有说上来.抱着有问题必须解决的态度,我看了许多资料,原来如此... 为何一直知道jsonp,但一直迷迷糊糊的不明白 ...

  4. js便签笔记(13)——jsonp事实上非常easy【ajax跨域请求】

    前两天被问到ajax跨域怎样解决,还真被问住了,光知道有个什么jsonp,迷迷糊糊的没有说上来.抱着有问题必须解决的态度,我看了很多资料,原来如此.. . 为何一直知道jsonp,但一直迷迷糊糊的不明 ...

  5. js便签笔记(6)——jQuery中的ready()事件为何需要那么多代码?

    前言: ready()事件的应用,是大家再熟悉不过的了,学jQuery的第一步,最最常见的代码: jQuery(document).ready(function () { }); jQuery(fun ...

  6. js便签笔记(10) - 分享:json2.js源码解读笔记

    1. 如何理解“json” 首先应该意识到,json是一种数据转换格式,既然是个“格式”,就是个抽象的东西.它不是js对象,也不是字符串,它只是一种格式,一种规定而已. 这个格式规定了如何将js对象转 ...

  7. js便签笔记(10) - 分享:json.js源码解读笔记

    1. 如何理解“json” 首先应该意识到,json是一种数据转换格式,既然是个“格式”,就是个抽象的东西.它不是js对象,也不是字符串,它只是一种格式,一种规定而已. 这个格式规定了如何将js对象转 ...

  8. js便签笔记(11)——浏览TOM大叔博客的学习笔记 part1

    1. 前言 这两天看了一下TOM大叔的<深入理解js系列>中的基础部分,根据自己的实际情况,做了读书笔记,记录了部分容易绊脚的问题.写篇文章,供大家分享. 2. 关于HTMLCollect ...

  9. js便签笔记(9)——解读jquery源码时记录的一些知识点

    近来一直利用业余时间在看jquery2.1.1源码,大约看了两千行了.平时看的时候,做了一些笔记,贴出来分享. 1. Array.prototype.slice.call 可以将伪数组转化为真正的数组 ...

随机推荐

  1. python使用数据库的一些操作

    学py感觉还是用linux操作系统比较好,下载安装mysql很简单,linux里面都有自带的,但是要用python去用mysql我们就得安装一下他的模块,因为python里面没有自带他的模块,用yum ...

  2. noip2017d2t2

    看数据范围想到状压,我们知道最后是选出一颗生成树,但边权的计算有一些有趣: 我们先选一个点做根:然后就发现边的权和深度有关:那我们按深度dp;即按层dp; dp[i][s]表示前i层选的点集为s,转移 ...

  3. C#将XML转换成JSON 使用 JavaScript 将 XML 转成 JSON

    如何在ASP.NET中用C#将XML转换成JSON [JavaScript]代码 // Changes XML to JSON function xmlToJson(xml) { // Create ...

  4. 《mysql必知必会》学习_sql文件导入数据库_20180724_欢

    解决问题1:MySQL中导入sql文件. 步骤1:show databases;#看看我有什么数据库 步骤2:use hh;#我要用hh这个数据库,返回database changed说明打开成功. ...

  5. codeforces 455E

    题目:http://codeforces.com/problemset/problem/455/E 题意:给定数组a,及f的定义: f[1][j] = a[j];  1 <= j <= n ...

  6. Web应用安全之Response Header里的敏感信息

    Web应用安全之Response Header 文/玄魂 目录 Web应用安全之Response Header 前言 1.1  那些敏感的header 1.2 删除敏感的header 1.2.1 删除 ...

  7. android.os.NetworkOnMainThreadException的解决方案

    06-24 18:04:36.857: E/AndroidRuntime(22251): FATAL EXCEPTION: main 06-24 18:04:36.857: E/AndroidRunt ...

  8. php file_get_contents fopen 连接远程文件

    使用file_get_contents和fopen必须空间开启allow_url_fopen. 方法: 编辑php.ini,设置allow_url_fopen =true On,allow_url_f ...

  9. C# webservice服务跟踪调试方法(转)

    1.新建网站,添加服务,并创建服务. 2.打开internet 信息服务管理器,添加网站,映射到创建的服务所在网站的目录. 3.打开服务所在网站的解决方案,进行配置. 1) 设置启动选项 选择启动操作 ...

  10. Linux下安装MySQL以及一些小坑

    第一次写博客,各位凑合着看吧(假装有人看). 我这里使用的是centos7. 1.首先打开终端,查看有没有安装过MySQL: [root@localhost lyp]# rpm -qa | grep ...