首页 > 网站 > WEB开发 > 正文

第十一章:事件系统

2024-04-27 14:06:12
字体:
来源:转载
供稿:网友

第十一章:事件系统

事件系统是一个框架非常重要的部分,用于响应用户的各种行为。浏览器提供了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某些私有实现无法支持,主要有以下事件:

DOMActivateDOMAttrModifiedDOMAttributeNameChangedDOMCharacterDataModifiedDOMContentLoadedDOMElementNameChangedDOMFocusInDOMFocusOutDOMMouseScrollDOMNodeInsertedDOMNodeInsertedIntoDocumentDOMNodeRemovedDOMNodeRemovedFromDcouemntDOMSubtreeModifiedMozMousePixelScroll

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的存在太多差异了,有的无法对上号,比如currentTarget5.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 IDsaddEvent.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的事件模块发端于Dean Edward的addEvent,然后它不断吸收社区的插件与补丁,发展成为非常棒的事件系统。其中不得不提的是事件代理与事件派发机制。

早在07年,Brandon Aaron为jQuery写了一个划时代的插件,叫livequery,它可以监听后来插入的元素的事件。比如说,一个表格,我们为tr元素绑定了mouSEOver/mouseout事件时,只有十行代码,然后我们又动态加载了20行,这20个tr元素同样能执行mouseover/mouseout回调。魔术在于,它并没有把事件侦探器绑定在tr元素上,而是绑定在最顶层的document上,然后通过事件冒泡,取得事件源,判定它是否匹配给用户给定的CSS表达式,才执行用户回调、具体参考它的github:

https://github.com/brandonaaron/livequery

如果一个表格有100个 tr元素,每个都要绑定mouseover/mouseout事件,改成事件代理的方式,可以节省99次绑定,这优化很好,更何况它能监听将来添加的元素,因此被立马吸收到jquery1.3中去,成为它的live方法,再把一些明显的bug修复了。jquery1.32成为最受欢迎的版本。与后来的jquery1.42都是历程碑式的。

不过话说回来,live方法需要对某些不冒泡的事件做些处理,比如一些表单事件,有的只能冒泡的form,有的只能冒泡到document,有的根本就不冒泡。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表