1 基本事件处理
事件句柄和this关键字:
在事件句柄被调用时,文档元素是作为产生事件的元素的方法调用的,所以关键字this引用了那个目标元素
事件句柄的作用域:
函数在定义它们的作用域中运行,而不在调用它们的作用域中运行。在为标记的HTML属性设置js代码串时,其实是定义了一个函数,而以这种方式定义的事件处理函数的作用域与用常规方法定义的全局js函数不同。这意味着定义为HTML属性的事件句柄所执行的作用域和其他函数的作用域不同
定义为HTML属性的事件句柄的作用域链头是调用对象,传递给事件句柄的所有参数都是在这里定义的,它们就和事件句柄主体中声明的局部变量一样。但事件句柄的作用域链中的下一个对象却并非全局对象,而是触发事件句柄的对象。
例如 使用<input>标记在HTML表单中定义了一个Button对象,然后使用onclick属性定义了一个事件句柄。如果该事件句柄的代码使用了一个名为form的变量,那么该变量会被解析为Button对象的form属性。
在定义作为HTML属性的事件句柄时,最安全的方法是使这种句柄尽量简单。理想的方法是让它们只调用在别的地方定义的全局函数,并可能返回下面的结果:
这样一个简单的事件句柄仍旧用一个不寻常的作用域链执行,但为了保持代码简短,尽量避免长作用域链带来的麻烦。由于函数在定义它们的作用域中运行,而不在调用它们的作用域中运行。所以即使从不寻常的作用域中调用了validateForm()方法,仍旧可以在它自己的全局作用域中执行,而不会产生任何混淆。
最后,记住,关于事件句柄作用域的完整讨论只适用于定义为HTML属性的事件句柄。如果把一个函数赋予适当的js事件句柄属性来设置事件句柄,那么根本不涉及特殊的作用域链,函数在定义它的作用域中执行,这几乎总是全局作用域,除非它是一个嵌套函数,在这种情况下,作用域链又变得有趣了。
2 2级DOM中的高级事件处理
事件传播:
在0级DOM事件模型中,浏览器把事件分派给发生事件的文档元素。如果那个对象具有适合的事件句柄,就运行这个句柄,除此之外不用执行其他操作。
在2级DOM这种高级事件模型中,当事件发生在文档元素上时,目标事件句柄被触发。此外目标的每个祖先元素也有机会处理那个事件。事件传播分三个阶段
一、捕捉阶段,事件从Document对象沿着文档树向下传播给目标节点。如果目标的任何一个祖先专门注册了捕捉事件句柄,那么在事件传播过程中就会运行这些句柄。
二、 直接注册在目标上的适合的事件句柄将运行
三、起泡阶段,事件从目标元素向上传播回或起泡回Document对象的文档层次。
这种事件传播可帮助事件处理代码中心化,1级DOM展现所有的文档元素,允许事件在任何元素上发生。这意味着注册事件句柄的地方比老式的0级事件模型多得多。
假定想为每个<p>标记都注册一个onmouseover事件句柄,只在Document对象上注册一个onmouseover,然后在事件传播的捕捉或起泡阶段处理这些事件即可。
关于事件传播,有一个重要细节。在0级模型中,只能为特定对象的特定类型的事件注册一个事件句柄。但在2级模型中可为特定对象的特定类型事件注册任意多个,处理器函数。这同样适用于事件目标的祖先,它们的处理函数将在事件传播的捕捉阶段或起泡阶段调用。
事件句柄的注册:
2级事件模型中,可以调用对象的addEventListener()方法为特定元素注册事件句柄。
第一个参数是要注册句柄的事件类型名
第二个参数是句柄函数
第三个参数是一个布尔值。若值为true,则指定的事件句柄将在事件传播的捕捉阶段用于捕捉事件。若为false,则事件句柄就是常规的,当事件直接发生在对象上,或发生在元素的子女上,又向上起泡到该元素时,该句柄将被触发。
在2级模型中,若给同一对象同一类型的事件注册了多个处理函数,那么在该类型的事件在那个对象上发生时,被注册的所有函数都将被调用,但函数的调用顺序不确定。
Event接口
当事件发生时,2级DOM API提供了事件的额外信息,作为传递给事件句柄的对象的属性。
Event
type:发生的事件的类型。该属性值是事件类型名,与注册事件句柄时使用的字符串值相同
target:发生事件的节点,可能与currentTarget不同
currentTarget:发生当前正在处理的事件的节点(如当前正在运行事件句柄的节点)。如果在传播过程的捕捉阶段或起泡阶段处理事件,这个属性的值就与target属性的值不同。
eventPhase:一个数字,指定当前所处的事件传播过程的阶段。它的值是常量,可能值包括Event.CAPTURE_PHASE,Event.AT_TARGET或Event.BUBBLING_PHASE
timeStamp:一个Date对象,声明了事件何时发生
bubbles:一个布尔值,声明该事件是否在文档树中起泡
cancelable:一个布尔值,声明该事件是否具有能用preventDefault()方法取消的默认动作
除了这7个属性,Event接口定义了两个方法,stopPropagation()和preventDefault(),前者阻止事件从当前正在处理它的节点传播。后者阻止浏览器执行与事件相关的默认动作。
UIEvent
UIEvent接口是Event接口的子接口,它定义的事件对象类型要传递给DOMFocusIn、DOMFocusOut和DOMActivate类型的事件。
view:发生事件的Window对象
detail:一个数字,提供事件的额外信息。对于click事件、mousedown事件和mouseup事件,这个字段代表点击的次数,1代表点击一次,2代表双击,3代表点击三次(注意,每次点击生成一个事件,但如果多次点击的间隔足够近,就可以用detail值说明它。简而言之,detail值为2的鼠标事件前面总是有一个detail值为1的鼠标事件)。对于DOMActivate事件,这个字段的值为1,表示正常激活,2表示超级激活,如双击鼠标或同时按下Shift键和Enter键
MouseEvent
MouseEvent接口继承Event接口和UIEvent接口的所有属性和方法,此外还定义了下列属性:
button:一个数字,声明在mousedown、mouseup和click事件中,哪个鼠标键改变了状态。值0、1、2分别代表左键、中间键和右键。这个属性只在鼠标键状态改变时使用,例如,在mousemove事件中,它不能用来汇报按键是否被按下并保持住了
altKey、ctrlKey、metaKey和shiftKey
这4个布尔值声明在鼠标事件发生时,是否按住了Alt键、Ctrl键、Meta键或Shift键。与button属性不同,这些键盘键属性对任何鼠标事件类型都有效。
clientX、clientY
这两个属性声明鼠标指针相对于客户区域或浏览器窗口的X坐标和Y坐标。注意,这两个坐标不考虑文档滚动,如果事件发生在窗口的顶部,无论文档滚动了多远,clientY都是0.
screenX、screenY
这两个属性声明了鼠标指针相对于用户显示器的左上角的X坐标和Y坐标。如果计划在鼠标事件所在地打开一个新浏览器窗口,这两个值非常有用
relatedTarget
该属性引用与事件的目标节点相关的节点。对于mouseover事件来说,它是鼠标移到目标上时所离开的那个节点。对于mouseout事件,它是离开目标时鼠标进入的节点。对于其他类型的事件来说,这个属性没有用。
混合事件模型
支持2级模型的浏览器将继续支持0级事件模型,这意味着可以在文档中使用混合事件模型。
浏览器传递给事件句柄一个事件对象,事件句柄是通过用0级模型设置HTML属性或JS属性注册的。在事件句柄定义为HTML属性时,它将隐式地转换成一个函数,该函数有一个参数,名为event。这意味着这样一个事件句柄可以用标识符event引用事件对象。
3 IE事件模型
IE事件模型介于0级模型和标准2级DOM模型之间,包括Event对象,该对象提供发生的事件的详细情况。但Event对象不是传递给事件句柄函数,而是作为Window对象的属性。IE模型支持起泡形式的事件传播,但不支持DOM模型的捕捉形式的事件传播(尽管IE5及其后的版本提供了专门的函数来捕捉鼠标事件)。在IE4中,注册事件句柄的方式与原始的0级模型注册方式相同,但IE5及其后版本中,可用专门但非标准的注册函数注册多个句柄。
IE Event对象
type : 字符串,声明发生的事件的类型,不带前缀"on"
srcElement : 发生事件的文档元素。与DOM Event对象的target属性兼容
button: 一个整数,声明被按下的鼠标键。值1、2、4分别表示左键、右键和中间键。如果按下了多个键,这些值将加在一起。若按下了多个键,这些值将加在一起
clientX、clientY: 声明事件发生时鼠标的坐标,其值是相对于包含窗口的左上角生成的。这些属性值和具有相同名字的2级DOM MouseEvent属性兼容
offsetX、offsetY: 声明鼠标指针相对于源元素的位置
altKey、ctrlKey和shiftKey : 声明发生鼠标事件时是否按住了alt,ctrl或shift键
KeyCode: 这个整数属性声明了keydown和keyup事件的键代码及keypress事件的Unicode字符。用String.fromCharCode()方法可以把字符代码转换成字符串
fromElement、toElement: fromElement声明mouseover事件中鼠标移动过的文档元素。toElement声明mouseout事件中鼠标移到的文档元素
cancelBubble: 设为true可阻止当前事件进一步起泡到包容层次的元素。与DOM的Event对象的stopPropagation()方法相同
returnValue: 设为false可阻止浏览器执行与事件相关的默认动作。它可替代由事件句柄返回false的老方法。它等价于DOM的Event对象的preventDefault()方法
作为全局变量的IE Event对象
虽然IE事件模型在Event对象中提供了事件的详细情况,但IE只是把事件对象传递给使用非标准的attachEvent()方法注册的句柄,其他的事件句柄调用的时候没有参数。IE不把Event对象作为参数传递给事件句柄,而通过全局Window对象的event属性访问Event对象。
attachEvent和detachEvent与addEventListener和removeEventListener区别在于:
1由于IE事件模型不支持事件捕捉,因此attachEvent和detachEvent方法只有两个参数,即事件类型和句柄函数
2传递给IE方法的事件句柄名字应该包括一个"on"前缀
3用attachEvent注册的函数将被作为全局函数调用,而不是作为发生事件的文档元素的方法,也就是说,在attachEvent()注册的事件句柄执行时,关键字this引用的是Window对象,而不是事件的目标元素
4attachEvent允许同一个事件句柄函数注册多次。当指定类型的一个事件发生时,注册函数被调用的次数和它被注册的次数一样多
IE中的事件起泡
IE中的事件起泡和2级DOM事件模型中的事件起泡之间的差别在于停止起泡的方式。IE Event对象没有DOM Event对象具有的stopPropagation()方法,所以阻止事件起泡需把Event对象的cancelBubble属性设为true
捕获鼠标事件
要实现涉及到拖拽鼠标的任何用户接口,能够捕获鼠标事件以便能够正确处理鼠标拖拽而不管用户拖拽了什么,这一点是很重要的。在IE5及其后版本中,通过setCapture和releaseCapture方法来实现
setCapture和releaseCapture是所有HTML元素的方法。当在一个元素上调用setCapture时,所有后续的鼠标事件都被引导到这个元素,并且这个元素的句柄可以在这些事件起泡前处理它们。注意,这只对鼠标事件适用,并且包括所有和鼠标相关的事件:mousedown,mouseup,mousemove,mouseover,mouseout,click和dblclick
当调用setCapture的时候,鼠标事件专门分派,直到调用releaseCapture或者捕获被中断。如果web浏览器失去焦点,鼠标事件可能会中断,会出现一个alert()对话框,显示一个系统菜单,或者类似的情况。如果发生了某种这样的事情,setCapture调用所基于的元素会接收到一个onlosecapture事件,通知它不再会接收到捕获的鼠标事件
在大多数常见的情形中,setCapture调用来作为对一个mousedown事件的响应,以保证后续的mousemove事件能够被同一元素所接收。元素执行其拖拽操作来响应mousemove事件,并且调用releaseCapture()来响应一个捕获的mouseup事件
attachEvent和this关键字
attachEvent作为全局函数调用,而不是作为发生事件的文档元素的方法,这意味着,关键字this引用的是全局窗口对象。就其自身而言,这还不是个问题,但考虑到IE事件对象没有等价的DOM currentTarget属性,情况就变得复杂了。srcElement指定了产生这一事件的元素,但如果事件已经起泡,这可能和处理事件的元素有所不同
若想编写一个通用的可在任何元素上注册的事件句柄,并且如果这个句柄需要知道它注册于哪个元素上,就不能用attachEvent()来注册该句柄。必须使用0级事件模型来注册句柄或者围绕句柄定义一个包围函数并注册这个包围函数
使用0级API的问题是它不允许多个句柄函数被注册,并且使用闭包的问题在于它们会导致IE中的内在泄漏。
事件句柄和内存泄漏
正如8.8.4.2中所说,当使用嵌套函数作为事件句柄时,IE(至少到IE6)很容易遭受一种内在泄漏,如下:
function addValidationHandler(form){ //为一个表单添加事件句柄
}
当调用这个函数时,它为指定的表单元素添加一个事件句柄,这个事件句柄定义为一个嵌套函数,尽管函数本身没有引用任何表单元素,但作为闭包的一部分而捕获的它的作用域会引用。结果一个表单元素引用了一个js Function对象,并且这个对象通过其作用域链引用回表单对象。这种循环引用导致了IE中的内存泄漏
这一问题的解决方案是:在针对IE编程的时候,有意避免使用嵌套的函数。另一种解决方案是,小心地移除所有响应一个onunload事件的事件句柄
示例:具有IE兼容性的事件模型
定义了两个函数,Handler.add()和Handler.remove(),来从一个指定的元素增加和移除事件句柄。对支持addEventListener的平台,这些函数只是围绕标准方法的包围方法。在IE 5及其后的版本,例子定义了弥补不兼容问题的手段:
1 事件句柄被他们所注册的元素的方法调用
2 事件句柄被传递给一个模拟的事件对象,该对象尽可能地和DOM标准事件对象相匹配
3 事件句柄的重复注册被忽略
4 所有句柄都在文档卸载上注册,以防止IE中的内在泄漏
用于IE的一个事件兼容层:
var Handler = {};
if(document.addEventListener){
}
else if(document.attachEvent){
}