第十一章:事件系统

事件系统是一个框架非常重要的部分,用于响应用户的各种行为。
浏览器提供了3个层次的api,用于响应用户的各种行为。

1.最原始的是写在元素标签内。
2.再次是脚本内,以el.onXXX = function绑定的方式,统称为DOM0事件系统。
3.最后是多投事件系统,一个元素的同一类型事件可以绑定多个回调,统常称为DOM2事件系统。

由于浏览器大战,现存两套API。

IE与opera:
绑定事件:el.attachEvent("on"+ type, callback)
卸载事件:el.detachEvent("on"+ type. callback)
创建事件:el.document.createEventObject()
派发事件:el.fireEvent(type,event)

w3c:

绑定事件:el.addEventListener(type,callback,[phase])
卸载事件:el.removeEventListener(type,callback,[phase])
创建事件:el.createEvent(types)
初始化事件:event.initEvent()
派发事件:el.dispatchEvent(event)

从api的数量与形式来看,w3c提供的复杂很多,相对于也强大很多,下面我们将逐一分析

首先我们先来几个简单的例子,没必要动用框架。不过事实上,整个事件系统就建立在它们的基础上。

    function addEvent (el, callback, useCapture) {
        if(el.dispatchEvent){//w3c优先
            el.addEventListener(type, callback, !!useCapture );
        } else {
            el.attachEvent( "on"+ type, callback );
        }
        return callback; //返回callback方便卸载时用
    }

    function removeEvent (el, type, callback, useCapture) {
        if (el.dispatchEvent) { //w3c优先
            el.removeEventListener (type, callback, !!useCapture);
        } else {
            el.detachEvent( "on"+type, callback )
        }
    }

    function fireEvent (el, type, args, event) {
        args = args || {}
        if (el.dispatchEvent) { //w3c优先
            event = document.createEvent("HTMLEvents");
            event.initEvent(type, true, true);
        } else {
            event = document.createEventObject();
        }

        for (var i in args) if (args.hasOwnProperty(i)) {
            event[i] = args[i]
        }

        if (el.dispatchEvent) {
            el.dispatchEvent(event);
        } else {
            el.fireEvent(‘on‘+type , event)
        }
    }

一,onXXX绑定方式的缺陷

onXXX既可以写在html标签内,也可以独立出来,作为元素节点的一个特殊属性来处理,不过作为一个古老的绑定方式,它很难预料到人们对这方面的扩展。

总结下来有以下不足:

1.onXXX对DOM3新增的事件或FF某些私有实现无法支持,主要有以下事件:

DOMActivate
DOMAttrModified
DOMAttributeNameChanged
DOMCharacterDataModified
DOMContentLoaded
DOMElementNameChanged
DOMFocusIn
DOMFocusOut
DOMMouseScroll
DOMNodeInserted
DOMNodeInsertedIntoDocument
DOMNodeRemoved
DOMNodeRemovedFromDcouemnt
DOMSubtreeModified
MozMousePixelScroll

2.onXXX只允许元素每次绑定一个回调,重复绑定冲掉之前的绑定
3.onXXX在IE下回调没有参数,在其他浏览器回调的第一个参数是事件对象。
4.onXXX只能在冒泡阶段可用。

二,attachEvent的缺陷

attachEvent是微软在IE5添加的API,Opera也支持,也对于onXXX方式,它可以允许同一种元素同一种事件绑定多个回调,也就是所谓多投事件机制。但带来的麻烦只多不少,存在以下几点缺陷。

1.ie下只支持微软的事件系统,DOM3事件一概不支持。
2.IE下attchEvent回调中的this不是指向被绑定元素,而是window!
3.IE下同种事件绑定多个回调时,回调并不是按照绑定时的顺序依次触发的!
4.IE下event事件对象与w3c的存在太多差异了,有的无法对上号,比如currentTarget
5.IE还是只支持冒泡阶段。

关于事件对象,w3c是大势所趋,在IE9支持W3c那一套API时,这对我们实现事件代理非常有帮助。

三,addEventListener的缺陷

w3c这一套API也不是至善至美,毕竟标准总是滞后于现实,剩下的标准浏览器各有自己的算盘,它们之间也有不一致的地方。

1.新事件非常不稳定,可能还有普及就开始被废弃,在早期的sizzle选择器引擎中,有这么几句。

    document.addEventListener("DOMAttrModified", invalidate, false);
    document.addEventListener("DOMNodeInserted", invalidate, false);
    document.addEventListener("DOMNodeRemoved", invalidate, false);

现在这三个事件被废弃了(准确的说,所有变动事件都完蛋了),FF14和chrome18开始使用MutationObserver代替它。

2.Firefox不支持focusin,focus事件,也不支持DOMFocusIn,DOMFocusOut,现在也不愿意用mouseWheel代替DOMMouseScroll。chrome不支持mouseenter与mouseleave.

因此,不要以为标准浏览器就肯定实现了w3c标准事件,所有特征侦测必不可少。

3.第三个,第四个,第五个标准参数。

第三个参数,useCapture只有非常新的浏览器才是可选项。比如FF6或之前是可选的,为了安全起见,确保第三个参数为布尔。

4.事件成员的不稳定。
w3c是从浏览器商抄过来的,人家用了这么久,难免与标准不一致。

ff下event.timeStamp返回0的问题,这个bug,2004年就有人提交了,直到2011年才被修复。

Safari下event.target可能返回文本节点

event.defaultPrevented,event.isTrusted与stopImmediatePropagation方法,之前标准浏览器都统一用getpreventDefault方法做这个事情,在jQuery源码中,发现它是用isDefaultPrevented来处理。

isTrusted属性用于表示当前事件是否是由用户行为触发,比如是用一个真实的鼠标点击触发click事件,还是由一个脚本生成的(使用事件构造方法,比如event.initEvent)。isTrusted请多关注

5.标准浏览器没有办法模拟像IE6-8的proprtychange事件。

虽然标准的浏览器有input, DOMAttrModified,MutationObserver,但比起propertychange弱爆了。propertychange可以监听多种属性变化,而不单单是value值。另外它不区分attribute和property。因此,无论是通过el.xxx = yyy 还是el.setAttribute(xxx,yyy)都接触此事件。

http://www.cnblogs.com/rubylouvre/archive/2012/05/26/2519263.html (判断浏览器是否支持DOMAttrModified)

四,Dean Edward的addEvent.js源码分析

这是一个prototype时代早期出现的一个事件系统。jQuery事件系统源头。亮点如下:

1.有意识的屏蔽IE与w3c在阻止默认行为与事件传播接口的差异。
2.处理ie执行回调时的顺序问题
3.处理ie的this指向问题
4.没有平台检测代码,因为是使用最通用最原始的onXXX构建
5.完全跨浏览器(IE4与NS4)。

此处省略源码分析

http://dean.edwards.name/weblog/2005/10/add-event/

// written by Dean Edwards, 2005
// with input from Tino Zijdel, Matthias Miller, Diego Perini

// http://dean.edwards.name/weblog/2005/10/add-event/

function addEvent(element, type, handler) {
    if (element.addEventListener) {
        element.addEventListener(type, handler, false);
    } else {
        // 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) {
    if (element.removeEventListener) {
        element.removeEventListener(type, handler, false);
    } else {
        // delete the event handler from the hash table
        if (element.events && element.events[type]) {
            delete element.events[type][handler.$$guid];
        }
    }
};

function handleEvent(event) {
    var returnValue = true;
    // grab the event object (IE uses a global event object)
    event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || 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];
        if (this.$$handleEvent(event) === false) {
            returnValue = false;
        }
    }
    return returnValue;
};

function fixEvent(event) {
    // add W3C standard event methods
    event.preventDefault = fixEvent.preventDefault;
    event.stopPropagation = fixEvent.stopPropagation;
    return event;
};
fixEvent.preventDefault = function() {
    this.returnValue = false;
};
fixEvent.stopPropagation = function() {
    this.cancelBubble = true;
};

不过在Dean Edward对应的博文中就可以看到许多指正与有用的patch。比如说,既然所有的修正都是冲着IE去的,那么标准浏览器用addEventListener就行。有的还提到,在iframe中点击事件时,事件对象不对的问题,提交以下有用的补丁。

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

其中,第54条回复,直接导致了jQuery数据缓存系统的产生,为了避免交错引用产出的内存泄露,建议元素分配一个uuid,所有的回调都放在一个对象中存储。

但随着事件的推移,使用者发现onXXX在IE存在不可消除和弥补的内存泄露,因此,翻看jQuery早期的版本,1.01是照抄Dean Edward的,1.1.31版本,开始吸收54条uuid的建议,并使用attach/removeEventListener绑定事件——每个元素只绑定一次。然后所有回调都在类似handleEvent的函数中调用。

(此章篇幅较长,以下即将更新:)

五,jQuery1.8.2的事件模块概述

六,jQuery.event.add源码解读

七,jQuery.event.remove的源码解读

八,jQuery.event.dispatch的源码解读

九,jQuery.event.trigger的源码解读

十,jQuery对事件对象的修复

十一,滚轮事件的修复

十二,mouseenter与mouseleave事件的修复

十三,focus与focusout事件的修复

十四,旧版本下IE的submit的事件代理实现

十五,oninput事件的兼容性性处理

上一章:第十章:属性模块 下一章:第十二章:章异步处理

时间: 2024-10-10 09:24:38

第十一章:事件系统的相关文章

javascript高级程序设计 第十一章--DOM扩展

javascript高级程序设计 第十一章--DOM扩展DOM最主要的扩展就是选择符API.HTML5和Element Traversal Selectors API:定义了两个方法 querySelector() 和 querySelectorAll(),能够基于CSS选择符从DOM中取得元素.querySelector()方法接收一个CSS选择符,返回该模式匹配的第一个元素,querySelectorAll()接收的参数一样,但是返回NodeList实例: matchesSelector()

c++ primer 5th 笔记:第十一章

第十一章:关联容器 笔记 1. 关联容器和顺序容器有着根本的不同:关联容器中的元素是按关键字来保存和访问的.与之相对,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的. 2. 关联容器支持高效的关键字查找和访问,两个主要的关联容器类型是map和set.map中的元素是一些关键字-值对:关键字起到索引的作用,值则表示与索引相关联的数据.set中每个元素只包含一个关键字:set支持高效的关键字查询操作--检查一个给定关键字是否在set中. 3. 当从map中提取一个元素时,会得到一个pair

zabbix专题:第十一章 zabbix proxy分布式监控配置

zabbix专题:第十一章 zabbix proxy分布式监控配置             对Linux有兴趣的朋友加入QQ群:476794643 在线交流 本文防盗链:http://zhang789.blog.51cto.co zabbix proxy分布式监控配置 zabbix proxy可以代替zabbix server检索客户端的数据,然后把数据汇报给zabbix server,并且在一定程度上分担了zabbix server的压力.zabbix proxy可以非常简便的实现了集中式.分

第十一章 执行查询算法

第十一章      执行查询算法 基本概念: 三类查找方法:线性查找.树形查找.哈希表查找 动态查找表:在查找的同时,对表做修改操作(如插入和删除),则相应的表称为动态查找表 静态查找表:与动态查找表相反 查找过程中对关键字需要执行的平均比较次数(也称平均比较长度作为衡量一个查找算法优劣的标准 平均比较长度: 其中:n是结点的个数:pi是查找第i个结点的概率.若不特别申明,认为每个结点的查找概率相等,都为1/n:ci是找到第i个结点所需进行的比较次数. 线性查找: 基本思想:从表的一端开始,顺序

Linux与云计算——第二阶段 第一十一章:代理Proxy服务器架设—Squid代理服务器正向代理和客户端配置

Linux与云计算--第二阶段Linux服务器架设 第一十一章:代理Proxy服务器架设-Squid代理服务器正向代理和客户端配置 安装Squid 安装Squid来配置代理服务器. [1] 这是一个通用的转发代理配置 [[email protected] ~]# yum -y install squid [[email protected] ~]# vi /etc/squid/squid.conf # line 26: 添加一条新的ACL acl lan src 192.168.96.0/24

第十一章 读书笔记

第十一章  Linux驱动程序中的 并发控制 并发(concurrency)指的是多个执行单元同时.并行被执行.而并发的执行单元对共享资 源〈如硬件资摞.程序中的全局变量.静态变量等〉的访问很容易导致竞态条件( race conditions). 自旋锁并不关心锁定的|临界区究竟是怎样的操作,不管是读还是写,都只允许同时只有一个执 行单元可以极取自旋锁, 即使有多个单元同时读取临界区资源也会被锁住.实际上,对于并发访问 共享资源时,多个执行单元同时读取它是不会有任何问题的.为了解决这个问题,自旋

JS复习:第十、十一章

第十章 NodeList是一种类数组对象,用于保存一组有序的节点,可以通过位置来访问这些节点,但它并不是Array实例,将其转化为数组的方法: function converToArray(nodes){ var array = null; try{ array = Array.prototype.slice.call(nodes,0); //slice方法的参数 }catch(ex){ array = new Array(); for(var i = 0,len = nodes.length

第十一章 异常,日志,断言和调试

第十一章 异常,日志,断言,调试 由于程序的错误或一些外部环境的影响造成用户数据的丢失,用户就有可能不再使用这个程序了.为了避免,应该做到以下几点: 向用户通告错误 保存所有的操作结果 允许用户以适当的形式退出程序. 11.1 处理异常 当由于出现错误而使得某些操作没有完成,程序应该: 返回到一种安全状态,并能够让用户执行一些其他命令:或者 允许用户保存所有操作的结果,以适当的方式终止程序 异常处理的任务就是将控制权从错误产生的地方转移给能够处理这种错误的处理器.程序中可能出现的错误和问题: 用

jQuery第十一章

第十一章 一.jQuery性能优化 1.使用最新版本的jQuery类库. 2.使用合适的选择器 (1)$(“#id”) :使用id来定位DOM元素是最佳提高性能的方式. (2)$(“p”) :标签选择器的性能也不错. (3)$(“.class”) :这种方法比较复杂 (4)$(“[attribute = value]”) :利用属性来定位DOM元素会使用DOM搜索的方式来达到效果. (5)$(“ : hidden”) :利用伪选择器会带来较大的性能问题. 3.缓存对象. 4.循环DOM操作. 5

《javascript设计模式》笔记之第十章 和 第十一章:门面模式和适配器模式

第十章:门面模式 一:门面模式的作用 简化已有的api,使其更加容易使用 解决浏览器的兼容问题 二:门面模式的本质 门面模式的本质就是包装已有的api来简化操作   三:门面模式的两个简单例子 下面这个例子就是一个简单的门面模式,用来处理事件的兼容性问题: function addEvent(el, type, fn) { if (window.addEventListener) { el.addEventListener(type, fn, false); } else if (window.