DOM的事件模型

  DOM的事件模型是官方组织制订的事件模型,因此,它才是“正统”的事件模型, Firefox、 Opera、Chrome等浏览器都支持这种事件模型。通常我们进行JavaScript编程时,至少需要考虑两种模型:一种是Internet Explorer模型,它是事实规范;另一种是DOM模型,它是行业规范,Firefox、Opera、Chrome等浏览器都会遵守该规范。

绑定事件处理器

  DOM也提供了一种事件绑定机制,这种机制和Internet Explorer的attachEvent()方法类似,但是有自己独特的语法。DOM所提供的事件绑定方法是addEventListener(),该方法的语法格式如下。
  ➢ objectTarget.addEventListener("eventType", handler, captureFlag):该方法为objectTarget绑定事件处理器handler,其第一个参数是事件类型字符串(将前面的事件属性去掉前缀“on”,例如click、mousedown、keypress等);第二个参数是事件处理函数;第三个参数用于指定监听事件传播的哪个阶段(true表示监听捕获阶段,false表示监听冒泡阶段)。
  与addEventListener()方法相对应,DOM也提供了一个方法用于删除事件处理器,该方法为removeEventListener,其语法格式如下:
  ➢ objectTarget.removeEventListener("eventType", handler, captureFlag):该方法为objectTarget删除事件处理器handler,参数与addEventListener()方法的3个参数完全类似。

例子
<!DOCTYPE html>  
<html>  
<head>  
    <meta http-equiv="Content-Type" content="text/html; charset=GBK" />
    <title> DOM事件机制 </title>
</head>  
<body>  
<!-- 将测试的div元素 -->  
<div id="test">  
    <!-- div元素的子元素:按钮 -->
    <input id="testbn" type="button" value="单击我" />
</div>  
<hr />  
<div id="results"> </div>  
<script type="text/javascript">  
    // 事件处理函数
    var gotClick1 = function(event) 
    {
        // 该事件处理函数简单输出事件的当前对象
        document.getElementById("results").innerHTML += 
            "事件捕获阶段: " + event.currentTarget + "<br />";
    }
    // 事件处理函数
    function gotClick2(event) 
    {
        // 该事件处理函数简单输出事件的当前对象
        document.getElementById("results").innerHTML +=
            "事件冒泡阶段:" + event.currentTarget + "<br />";
    }
    // 为testbn按钮绑定事件处理函数(捕获阶段)
    document.getElementById("testbn")
        .addEventListener("click" , gotClick1 , true); 
    // 为test对象绑定事件处理函数(捕获阶段)
    document.getElementById("test")
        .addEventListener("click" , gotClick1 , true); 
    // 为testbn按钮绑定事件处理函数(冒泡阶段)
    document.getElementById("testbn")
        .addEventListener("click" , gotClick2 , false); 
    // 为按钮所在的div对象绑定事件处理函数(冒泡阶段)。
    document.getElementById("test")
        .addEventListener("click" , gotClick2 , false);
</script>  
</body>  
</html>  

  上面的代码中为“单击我”按钮和所在的<div.../>元素分别绑定了两个事件处理函数。绑定两个事件处理函数时,最后一个参数不同,两个参数的值分别为true和false,表明该元素在两个阶段都绑定对应的事件处理函数。
  事件捕获阶段的两个事件处理函数先被触发,而事件冒泡阶段的两个事件处理函数后被触发。而且,在捕获阶段,先触发<div.../>元素;而在冒泡阶段,则先触发<input.../>元素。这与DOM的事件传播机制有关。

访问事件对象

  前面已经提到,DOM事件模型与Internet Explorer事件模型访问事件对象的方式不同,在DOM的事件模型中,当浏览器检测到发生了某个事件时,将自动创建一个Event对象,并隐式地将该对象作为事件处理函数的第一个参数传入。

例子
<!DOCTYPE html>  
<html>  
<head>  
    <meta http-equiv="Content-Type" content="text/html; charset=GBK" />
    <title> 访问事件对象 </title>
</head>  
<body>  
    <button id="a">按钮</button>
    <script type="text/javascript">
        // 定义一个形参evt
        var clickHandler = function(evt)
        {
            // DOM的事件对象将作为第一个参数传入clickHandler对象
            alert(evt.target.innerHTML);
        }
        // 为按钮a绑定事件处理器
        document.getElementById("a").onclick = clickHandler;
    </script>
</body>  
</html>  

  在上面的代码中我们看到,clickHandler()函数包含了一个evt参数,但该函数从未被显式调用,而是被绑定为按钮a的事件监听器。在DOM事件模型中,当用户单击该按钮时,浏览器会将该单击事件封装成Event对象,并将该对象传给clickHandler()的evt参数。

  DOM事件模型和Internet Explorer事件模型访问事件对象的方式完全不同,如果需要写一个跨浏览器的程序,容易想到的做法是:将事件处理函数绑定到HTML元素,并将event显式作为参数传入事件处理函数。
  但将事件处理函数绑定到HTML元素属性不是一种好的做法。实际上,即使将事件处理函数绑定到DOM对象的属性,也一样可以实现跨浏览器。正如在前面Internet Explorer的拖放效果中所看到的,Internet Explorer事件模型与DOM事件模型有太多的地方存在差异,为了更好地实现跨浏览器,我们在访问这些有冲突的对象、属性和方法之前,应该首先判断该浏览器是否支持该对象、属性和方法。
  下面介绍一种可跨浏览器访问事件的方法。

例子
<!DOCTYPE html>  
<html>  
<head>  
    <meta http-equiv="Content-Type" content="text/html; charset=GBK" />
    <title> 跨浏览器访问事件 </title>
</head>  
<body>  
    <button id="a">按钮</button>
    <script type="text/javascript">
    // 定义一个形参evt
    var clickHandler = function(evt)
    {
        // 对于DOM事件模型,访问事件源用target属性
        if (evt)
        {
            alert(evt.target.innerHTML);
        }
        // 对于IE浏览器
        else
        {
            alert(window.event.srcElement.innerHTML);
        }
    }
    // 为按钮a绑定事件处理器
    document.getElementById("a").onclick = clickHandler;
    </script>
</body>  
</html>  
提示

  上面代码中clickHandler()函数的形参名不要叫event。如果该函数的形参名叫event,则该形参将会覆盖全局可用的event对象,从而可能导致在Internet Explorer中不能访问事件对象。

  DOM提供了一套完整的事件继承体系。DOM的事件继承图如下图所示。


IT料理

  DOM事件模型中的每个具体事件都是上面事件接口的一个实例。下面是具体的对应关系。
  ➢ Event:对应有abort、blur、change、error、focus、load、reset、resize、scroll、select、submit、unload等事件。
  ➢ MouseEvent:对应有click、mousedown、mousemove、mouseout、mouseover、mouseup等事件。
  ➢ UIEvent:对应有DOMActivate、DOMFocusIn、DOMFocusOut等事件。
  ➢ MutationEvent:对应有DOMAttrModified、DOMCharacterDataModified、DOMNodeInserted、DOMNodeInsertedIntoDocument、DOMNodeRemoved、DOMNodeRemovedFromDocument、DOMSubtreeModified等事件。
  在Event接口里定义了如下属性
  ➢ type:返回该事件的类型,该属性值与注册事件处理器时所用的事件类型字符串相同(例如click、mouseover等)。
  ➢ target:返回触发事件的事件源。
  ➢ currentTarget:返回事件当前所在的事件源。该属性值与target属性可以不同,如果在捕获或冒泡阶段处理该事件,则该属性值与target属性返回的对象并不相同。基本上,该属性可以代替事件处理器中的关键字this。
  ➢ eventPhase:返回该事件正处在哪个阶段,可能的值有Event.CAPTURINGPHASE(捕获阶段)、Event.ATTARGET或Event.BUBBLING_PHASE(冒泡阶段)。
  ➢ timeStamp:返回一个Date对象,代表事件的发生时间。
  ➢ bubbles:返回一个boolean值,用以表示该类事件是否支持冒泡。
  ➢ cancelable:返回一个boolean值,用以指定该事件是否有默认行为,且可以通过preventDefault()方法来取消该默认行为。
  UIEvent接口定义了如下两个属性
  ➢ view:返回window对象,也就是发生该事件的窗口。
  ➢ detail:返回一个数字,该数字可以提供一些附加意义。例如对click、mousedown和mouseup事件,event属性返回1代表单击,2代表双击,3代表三击(鼠标每次单击都会产生一个事件,如果两次单击的时间足够接近,它们就会变成一次双击事件)。
  MouseEvent接口继承了UIEvent,它不仅可以使用Event接口的所有属性,也可以访问UIEvent接口的全部属性,该接口里包含如下几个属性。
  ➢ button:返回一个数字,代表触发事件的鼠标键。其中0代表鼠标左键,1代表鼠标中键,2代表鼠标右键。只有当浏览者改变了鼠标键状态时才可以访问该属性,例如mousemove事件就不可访问该属性。
  ➢ altKey、ctrlKey、metaKey、shiftKey:这4个属性都返回boolean值,用于显示发生该鼠标事件时,是否同时按下了Alt、Ctrl、Meta或Shift功能键。
  ➢ clientX、clientY:返回鼠标事件的发生位置,该位置以浏览者的浏览器窗口作为坐标系。注意:该位置完全不考虑document的滚动位置,即使把浏览器滚动条拖到下面,只要鼠标事件在浏览器上方发生,clientY属性依然是0。DOM模型并没有提供标准方法来完成window坐标到document坐标之间的转换,所以开发者必须手动实现转换。在除IE之外的其他浏览器中,可以再为这两个属性分别添加window.pageXOffset和window.pageYOffset来完成window坐标到document坐标的转换。
  ➢ screenX、screenY:返回鼠标事件的发生位置,该位置以用户显示器作为鼠标位置的坐标系。当开发者试图在鼠标位置打开一个新的浏览器时,这两个属性比较有用。
  ➢ relatedTarget:返回该事件事件源的相关节点。对于mouseover事件,该属性值返回在鼠标划过某个HTML元素之前离开的HTML元素;对于mouseout事件,该属性值返回在鼠标离开某个HTML元素后立即进入的HTML元素。其他事件通常没有该属性。

注:本博客内容节选自李刚编著的疯狂HTML 5/CSS 3/JavaScript讲义 ,详细内容请参阅书籍。