事件传播

  DOM模型的事件先后沿着两个方向传播:在第一个阶段,也就是前面提到的事件捕获阶段,事件从最顶层的对象依次向下传播,因此先触发顶层对象的事件处理函数,然后依次向下,直到传播到事件所发生的最底层对象;接着进入第二个阶段,也就是前面提到的事件冒泡阶段,事件传播从底层一直上溯,直到最顶层的对象。
  整个DOM模型的事件传播可以分成两个阶段:捕获阶段和冒泡阶段。捕获阶段的事件触发器总是比冒泡阶段的触发器先触发:在事件捕获阶段,顶层对象的事件处理器先被触发;而在事件冒泡阶段,则是底层对象的事件触发器先被触发。
  DOM模型的事件传播示意图如下图所示。


IT料理

  为了阻止事件传播,DOM为Event对象提供了stopPropagation()方法,该方法的语法格式如下:
  ➢ event.stopPropagation():阻止event事件传播。如果我们在事件捕获阶段调用该方法阻止事件传播,则该事件根本不会进入事件传播阶段。
  一旦调用该方法,该事件的传播就将被完全停止。值得注意的是DOM模型的事件传播顺序:在事件捕获阶段,事件从顶层对象传播到事件发生的目标对象;而在事件冒泡阶段,事件从事件发生的目标对象向上传播到顶层对象。

例子
<!DOCTYPE html>  
<html>  
<head>  
    <meta http-equiv="Content-Type" content="text/html; charset=GBK" />
    <title> 阻止事件传播 </title>
</head>  
<body>  
友情链接:<br />
<!-- 目标超链接 -->  
<a id="mylink" href="http://www.baidu.com">百度</a>  
<div id="show"></div>  
<script type="text/javascript">  
    // 事件捕获阶段的处理函数
    var killClick1 = function(event)
    {
        // 取消默认事件的默认行为
        event.preventDefault();
        // 阻止事件传播
        event.stopPropagation();
        document.getElementById("show").innerHTML
            += '事件捕获阶段' + event.currentTarget + "<br />";
    }
    //事件冒泡阶段的处理函数
    var killClick2 = function(event)
    {
        // 取消事件的默认行为
        event.preventDefault();
        // 阻止事件传播
        event.stopPropagation(); 
        document.getElementById("show").innerHTML
            += '事件冒泡阶段' + event.currentTarget + "<br />";
    }
//    // 在事件捕获阶段,分别为超链接对象、document对象绑定事件处理函数。
//    document.addEventListener("click", killClick1, true);
//    document.getElementById("mylink").addEventListener(
//        "click", killClick1, true);
    // 在事件冒泡阶段,分别为超链接对象、document对象绑定事件处理函数。
    document.addEventListener("click", killClick2, false);
    document.getElementById("mylink").addEventListener(
        "click", killClick2, false);
</script>  
</body>  
</html>  

  根据上面的介绍,当事件传播处于捕获阶段时,从顶层对象向下传播,最先触发document对象的事件处理函数,该对象的事件处理函数阻止了事件传播,因而超链接对象上的事件处理函数不会触发,事件传播更不会进入事件冒泡阶段。
  如果将事件捕获阶段的事件处理函数注释掉,即只为document对象和超链接对象的事件冒泡阶段绑定事件处理函数,此时超链接对象上的事件处理函数将先被触发,然后阻止事件传播,而document对象上的事件处理函数将不会被触发。

  DOM实现了完备的事件处理机制,只要任何HTML元素在捕获阶段捕获了事件,并阻止了该事件的继续传播,事件就不会进入事件传播阶段,这对实现鼠标拖放等效果已经足够了。下面将实现一个跨浏览器的拖放效果,为了实现跨浏览器,程序在访问对象、属性或方法之前,总是先判断该浏览器是否支持该对象、属性或方法。HTML代码请参考IE拖拽模型中的代码。

例子
var drag = function(target, event)  
{
    // 定义开始拖动时的鼠标位置(相对window座标)
    var startX = event.clientX;
    var startY = event.clientY;
    // 定义将要被拖动元素的位置(相对于document座标)
    // 因为该target的position为absolutely,
    // 所以我们认为它的座标系是基于document的
    var origX = target.offsetLeft;
    var origY = target.offsetTop;
    // 因为后面根据event的clientX、clientY来获取鼠标位置时,
    // 只能获取windows座标系的位置,所以需要计算window座标系
    // 和document座标系的偏移。
    // 计算windows座标系和document座标系之间的偏移
    var deltaX = startX - origX;
    var deltaY = startY - origY;
    // 鼠标松开的事件处理器
    var upHandler = function(evt) 
    {
        // 对于IE事件模型,获取事件对象
        if (!evt) evt = window.event; 
        // 取消被拖动对象的鼠标移动(mousemove)和鼠标松开(mouseup)的事件处理器
        if (document.removeEventListener)
        {
            // DOM事件模型
            // 取消在事件捕获阶段的事件处理器
            document.removeEventListener("mouseup", upHandler, true);
            document.removeEventListener("mousemove", moveHandler, true);
        }
        else if (document.detachEvent) 
        {
            target.detachEvent("onlosecapture", upHandler);
            target.detachEvent("onmouseup", upHandler);
            target.detachEvent("onmousemove", moveHandler);
            target.releaseCapture( );
        }
        // 阻止事件传播
        stopProp(evt);
    }
    // 阻止事件传播(该函数可以跨浏览器)
    var stopProp = function(evt)
    {
        // DOM事件模型
        if (evt.stopPropagation)
        {
            evt.stopPropagation( );
        }
        // IE事件模型
        else
        {
            evt.cancelBubble = true;
        }
    }
    // 为被拖动对象的鼠标移动(mousemove)和鼠标松开(mouseup)注册事件处理器
    if (document.addEventListener)
    {
        // DOM事件模型
        // 在事件捕获阶段绑定事件处理器
        document.addEventListener("mousemove", moveHandler, true);
        document.addEventListener("mouseup", upHandler, true);
    }
    else if (document.attachEvent) 
    {
        // IE事件模型
        // 设置该元素直接捕获该事件
        target.setCapture();
        // 为该元素鼠标移动时绑定事件处理器
        target.attachEvent("onmousemove", moveHandler);
        // 为鼠标松开时绑定事件处理器
        target.attachEvent("onmouseup", upHandler);
        // 将失去捕获事件当成鼠标松开处理。
        target.attachEvent("onlosecapture", upHandler);
    }
    // 阻止事件传播
    stopProp(event);
    // 取消事件默认行为
    if (event.preventDefault)
    {
        // DOM事件模型
        event.preventDefault( ); 
    }
    else
    {
        // IE事件模型
        event.returnValue = false;
    }
    // 鼠标移动的事件处理器
    function moveHandler(evt)
    {
        // 对于IE事件模型,获取事件对象
        if (!evt) evt = window.event; 
        // 将被拖动元素的位置移动到当前鼠标位置。
        // 先将window座标系位置转换成document座标系位置,再修改目标对象的CSS位置。
        target.style.left = (evt.clientX - deltaX) + "px";
        target.style.top = (evt.clientY - deltaY) + "px";
        // 阻止事件传播
        stopProp(evt);
    }
}

转发事件

  DOM提供了dispatchEvent方法用于事件的转发,该方法属于Node对象,因此DOM的每个Node元素都可调用该方法,从而将事件直接转发到本节点。该方法的语法格式如下:
  ➢ target.dispatchEvent(Event event):将event事件转发到target上。
  与Internet Explorer事件模型里的重定向事件不同的是,dispatch()方法必须转发人工合成事件(Synthetic Event),不能直接转发系统创建的事件。
  DOM为创建人工合成事件提供了如下方法。
  ➢ document.createEvent(String type):该方法创建一个事件对象,其中type参数用于指定事件类型,普通事件可使用Events,UI事件可使用UIEvents,鼠标事件可使用MouseEvents。
  通过上面的方法得到一个事件后,可调用事件的如下方法来初始化。
  ➢ initEvent(String eventTypeArg,boolean canBubbleArg, boolean cancelableArg):用于初始化一个普通事件,第一个参数用于指定该事件类型,如click等;第二个参数用于指定该事件是否支持冒泡;第三个参数用于指定该事件是否有默认行为,且可通过preventDefault()方法取消该默认行为。
  ➢ initUIEvent(String typeArg, boolean canBubbleArg, boolean cancelableArg, Window viewArg, long detailArg):该方法的前3个参数的意义与上一个方法中的3个参数完全相同。后两个参数的意义与介绍UIEvent时的view、detail两个属性相同。
  ➢ initMouseEvent(String typeArg, boolean canBubbleArg, boolean cancelableArg, AbstractView viewArg, long detailArg, long screenXArg, long screenYArg, long clientXArg, long clientYArg, boolean ctrlKeyArg,boolean altKeyArg, boolean shiftKeyArg, boolean metaKeyArg, unsigned short buttonArg, Element relatedTargetArg):该方法的前5个参数的意义与上一个方法中的5个参数完全相同。后面参数的意义与介绍MouseEvent时的各属性相同。

例子
var drag = function(target, event)  
{
    // 定义开始拖动时的鼠标位置(相对window座标)
    var startX = event.clientX;
    var startY = event.clientY;
    // 定义将要被拖动元素的位置(相对于document座标)
    // 因为该target的position为absolutely,
    // 所以我们认为它的座标系是基于document的
    var origX = target.offsetLeft;
    var origY = target.offsetTop;
    // 因为后面根据event的clientX、clientY来获取鼠标位置时,
    // 只能获取windows座标系的位置,所以需要计算window座标系
    // 和document座标系的偏移。
    // 计算windows座标系和document座标系之间的偏移
    var deltaX = startX - origX;
    var deltaY = startY - origY;
    // 鼠标松开的事件处理器
    var upHandler = function(evt) 
    {
        // 对于IE事件模型,获取事件对象
        if (!evt) evt = window.event; 
        // 取消被拖动对象的鼠标移动(mousemove)和鼠标松开(mouseup)的事件处理器
        if (document.removeEventListener)
        {
            // DOM事件模型
            // 取消在事件捕获阶段的事件处理器
            document.removeEventListener("mouseup", upHandler, true);
            document.removeEventListener("mousemove", moveHandler, true);
        }
        else if (document.detachEvent) 
        {
            target.detachEvent("onlosecapture", upHandler);
            target.detachEvent("onmouseup", upHandler);
            target.detachEvent("onmousemove", moveHandler);
            target.releaseCapture( );
        }
        // 阻止事件传播
        stopProp(evt);
    }
    // 阻止事件传播(该函数可以跨浏览器)
    var stopProp = function(evt)
    {
        // DOM事件模型
        if (evt.stopPropagation)
        {
            evt.stopPropagation( );
        }
        // IE事件模型
        else
        {
            evt.cancelBubble = true;
        }
    }
    // 为被拖动对象的鼠标移动(mousemove)和鼠标松开(mouseup)注册事件处理器
    if (document.addEventListener)
    {
        // DOM事件模型
        // 在事件捕获阶段绑定事件处理器
        document.addEventListener("mousemove", moveHandler, true);
        document.addEventListener("mouseup", upHandler, true);
    }
    else if (document.attachEvent) 
    {
        // IE事件模型
        // 设置该元素直接捕获该事件
        target.setCapture();
        // 为该元素鼠标移动时绑定事件处理器
        target.attachEvent("onmousemove", moveHandler);
        // 为鼠标松开时绑定事件处理器
        target.attachEvent("onmouseup", upHandler);
        // 将失去捕获事件当成鼠标松开处理。
        target.attachEvent("onlosecapture", upHandler);
    }
    // 阻止事件传播
    stopProp(event);
    // 取消事件默认行为
    if (event.preventDefault)
    {
        // DOM事件模型
        event.preventDefault( ); 
    }
    else
    {
        // IE事件模型
        event.returnValue = false;
    }
    // 鼠标移动的事件处理器
    function moveHandler(evt)
    {
        // 对于IE事件模型,获取事件对象
        if (!evt) evt = window.event; 
        // 将被拖动元素的位置移动到当前鼠标位置。
        // 先将window座标系位置转换成document座标系位置,再修改目标对象的CSS位置。
        target.style.left = (evt.clientX - deltaX) + "px";
        target.style.top = (evt.clientY - deltaY) + "px";
        // 阻止事件传播
        stopProp(evt);
    }
}

取消事件的默认行为

  DOM也提供了取消事件默认行为的方法,DOM中的事件对象都提供了preventDefault()方法,该方法不需要参数,只要执行了给定事件的preventDefault方法,该事件的默认行为就将失效。该方法的语法格式如下:
  ➢ event.preventDefault():取消event事件的默认行为。
  preventDefault()方法虽然取消了事件的默认行为,但不会阻止事件传播,下面的代码为超链接和document在事件传播阶段绑定了事件处理函数。

例子
<!DOCTYPE html>  
<html>  
<head>  
    <meta http-equiv="Content-Type" content="text/html; charset=GBK" />
    <title> 阻止默认行为 </title>
</head>  
<body>  
    友情链接:<br />
    <a id="mylink" href="http://www.baidu.com">百度</a>
    <!-- 显示信息输出的div元素 -->
    <div id="show"></div>
    <script type="text/javascript">
        var killClicks = function(event)
        {
            // 取消事件的默认行为
            event.preventDefault();
            document.getElementById("show").innerHTML
                += "事件捕获阶段:" + event.currentTarget + "<br>";
        }
        // 为document对象绑定事件处理函数
        document.addEventListener("click", killClicks, true);
        // 为超链接绑定事件处理函数
        document.getElementById("mylink")
            .addEventListener("click", killClicks, true);
    </script>
</body>  
</html>  

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