DOM —— 事件

事件流

JavaScript与HTML之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器(或处理程序)来预定事件,以便事件发生时执行相应的代码。这种在传统软件工程中被成为观察员模式的模型,支持页面的行为(js代码)与页面的外观(HTML和CSS代码)之间的松散耦合。

事件最早是在IE3和Netscape Navigator2(以下简称网景)中出现的,当时作为分担服务器运算负载的一种手段。在IE4和网景4发布时,这两种浏览器都提供了相似不相同的API,这些API并存经过了好几个主要版本,DOM2级规范开始尝试以一种符合逻辑的方式来标准化DOM事件。IE9、Firefox、Opera、Safari和Chrome全部已实现了DOM2级事件模块的核心部分。IE8是最后一个仍然使用其专有事件系统的主要浏览器。

定义与由来
当浏览器发展到第四代时,浏览器开发团队遇到了一个很有意思的问题:页面的哪一部分会拥有某个特定的事件?想象一下,在一张纸上画一个同心圆,如果你把手指放在圆心上,那么你的手指指向的不是一个圆,而是纸上所有的圆。两家公司的浏览器开发团队看待浏览器事件方面还是一致的。如果你单击了某个按钮,他们都认为单击事件不仅仅发生在按钮上。换句话说,在单击按钮的同时,你也单击了按钮的容器元素,甚至也单击了整个页面。
事件流描述的是从页面中接受事件的顺序。但有意思的是,IE和网景开发团队居然提出了差不多完全相反的事件流的概念。IE的事件流是事件冒泡,而网景的事件流是事件捕获流。


事件冒泡

IE的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。以下面的HTML页面为例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Event Bubbling Example</title>
</head>
<body>
    <div id="myDiv">Click Me</div>
</body>
</html>

如果单击了页面中的<div>元素,那么这个click事件会按照如下顺序传播:

  1. <div>
  2. <body>
  3. <html>
  4. document

也就是说,click事件首先在<div>元素上发生,而这个元素就是我们单击的元素。然后,click事件沿DOM数向上传播,在每一级节点上都会发生,直至传播到document对象。


事件冒泡流

所有浏览器都支持事件冒泡,但在具体实现上还是有一些差别。IE5.5及更早版本会跳过<html>元素直接到document对象。IE9、Chrome、Safari则将事件一直冒泡到window对象。


事件捕获

网景团队提出的另一种事件流叫做事件捕获(event capturing)。事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。如果仍以之前HTML代码演示捕获例子,那么单击click元素就会以下列顺序触发click事件:

  1. document
  2. <html>
  3. <body>
  4. <div>

在事件捕获过程中,document对象首先接收到click事件,然后事件沿DOM数依次向下,一直传播到事件具体目标,即<div>元素。

事件捕获

虽然事件捕获是网景唯一支持的事件流模型,但IE9、Safari、Chrome、Opera和Firefox目前也都支持这种事件流模型。尽管”DOM2级事件“规范要求事件应该从document对象开始传播,但这些浏览器都是从window对象开始捕获事件的。
由于老版本浏览器不支持,很少有人使用事件捕获,一般都使用事件冒泡。


DOM事件流

DOM2级事件规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。以前面简单的HTML页面为例,单击<div>元素会按照下图所示顺序触发事件。


DOM事件流

在DOM事件流中,实际的目标<div>元素在捕获阶段不会接收到事件。这意味在捕获阶段,事件从document到<html>再到<body>后就停止了。下一个阶段是”处于目标“阶段,于是事件在<div>上发生,并在事件处理中被看成冒泡阶段的一部分。然后,冒泡阶段发生,事件又传播回文档。

IE8及更早版本不支持DOM事件流。


事件处理程序

事件就是用户或浏览器自身执行的某种动作。诸如:click、load和mouseover,都是事件的名字。而响应某个个事件的函数叫做事件处理程序(或事件侦听器)。

其实JS与HTML的交互就是事件处理程序执行的结果。

事件处理程序的名字以"on"开头,因此click事件的事件处理程序(click事件侦听器)就是onclick,load事件的事件处理程序(load事件侦听器)就是onload。

为事件指定处理程序的方式有好几种(面试)


HTML事件处理程序

某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。这个特性的值应该是能够执行的JavaScript代码。如:

    <input type="button" value="Click Me" onclick="alert('Clicked')">

当单击这个按钮时,就会显示一个警告框。这个操作是通过指定onclick特性一些JavaScript代码作为它的值来定义的。由于这个值是JavaScript,因此不能在其中使用未经转义的HTML语法字符。为了避免使用HTML实体,这里使用单引号,如果想要使用双引号,可以这样改写:

    <input type="button" value="Click Me" onclick="alert(&quot;Clicked&quot;)">

在HTML中定义的事件处理程序可以包含要执行的具体动作,也可以调用在页面其他地方定义的脚本:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Event Bubbling Example</title>
    <script src="./js/fuck.js"></script>
</head>
<body>
    <input type="button" value="Click Me" onclick="showMessage()">
    <script>
        function showMessage() {
            alert("hello world");
        }       
    </script>
</body>
</html>

单击按钮调用showMessage()函数,这个函数是在一个独立<script>元素中定义的,当然也可以被包含在一个外部文件中。事件处理程序中的代码在执行时,有权访问全局作用域中任何代码

这样指定事件处理程序具有一些独到之处。首先,这样会创建一个封装着元素属性值的函数。这个函数中有一个局部变量event,也就是事件对象:

    <input type="button" value="Click Me" onclick="console.log(event.type)">    
    <!--    输出click    -->

event为事件对象,我们打印出来看看里面有啥

    <input type="button" value="Click Me" onclick="console.log(event)">
null: MouseEvent {isTrusted: true, screenX: 261, screenY: 236, clientX: 38, clientY: 17, …}
altKey: false
bubbles: true
button: 0
buttons: 0
cancelable: true
cancelBubble: false
clientX: 38
clientY: 17
composed: true
ctrlKey: false
currentTarget: null
defaultPrevented: false
detail: 1
eventPhase: 0
fromElement: null
isTrusted: true
layerX: 38
layerY: 17
metaKey: false
movementX: 0
movementY: 0
offsetX: 28
offsetY: 5
pageX: 38
pageY: 17
path: Array(5) [input, body, html, …]
relatedTarget: null
returnValue: true
screenX: 261
screenY: 236
shiftKey: false
sourceCapabilities: InputDeviceCapabilities {firesTouchEvents: false}
srcElement: input
target: input
timeStamp: 3469.8000000207685
toElement: input
type: "click"
view: Window {postMessage: , blur: , focus: , …}
which: 1
x: 38
y: 17
__proto__: MouseEvent {screenX: <accessor>, screenY: <accessor>, clientX: <accessor>, …}

通过event变量,可以直接访问事件对象,你不用自己定义它,也不用从函数的参数列表中读取。在这个函数内部,this值等于事件的目标元素。如:

    <input type="button" value="Click Me" onclick="console.log(this.value)">    
    <!--    输出 "Click Me"-->

关于这个动态创建的函数,另一个有意思的地方是它扩展作用域的方式,在这个函数内部,可以像访问局部变量一样访问document及该元素本身的成员。这个函数使用with像下面这样扩展作用域:

        function() {
            with(document) {
                with(this) {
                    // 元素属性值
                }
            }
        }

这样一来,事件处理程序要访问自己的属性就容易多了。

    <input type="button" value="Click Me" onclick="alert(value)">   
    <!--    输出 "Click Me"    -->
总结(面试):

这种在HTML中指定事件处理程序有两个缺点。

时差问题

用户可能会在HTML元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。
比如,将click事件处理函数放在HTML下方,页面最底部,用户在函数解析完成前激活了click事件,就会引发错误。为此,很多HTML事件处理程序都会被封装在一个try-catch块中,以便错误不会浮出水面,如:

    <input type="button" value="Click Me" onclick="try{showMessage();}catch(ex){}"> 

这样,如果在showMessage()函数有定义之前单击按钮,用户不会看到JavaScript错误,因为在浏览器有机会处理错误之前,错误就被捕获了。

作用域问题

另一个缺点是,这样扩展事件处理程序的作用域链在不同浏览器导致不同结果。不同JavaScript引擎遵循的标识符规则略有差异,很可能会在访问非限定对象成员时出错。

分离原则

通过HTML指定事件处理程序最后一个缺点是HTML与JavaScript代码紧密耦合。如果要更换事件处理程序,就要改动HTML代码和JavaScript代码,而这正是开发人员摒弃HTML事件处理程序,转而使用JavaScript指定事件处理程序的原因所在。

不要使用HTML指定事件处理程序

DOM0级事件处理程序

通过JavaScript指定事件处理方式的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种为事件处理程序赋值的方法是在第四代Web浏览器中出现。

  • 简单
  • 具有跨浏览器优势

每个元素(包括window和document)都有自己的事件处理程序属性,这些属性通常全部小写,例如onclick。将这种属性的值设置为一个函数,就可以指定事件处理程序。

        var btn = document.getElementById("myBtn");
        btn.onclick = function() {
            alert("Clicked");
        };

在此,我们通过文档对象取得了一个按钮的引用,然后为它指定了onclick事件处理程序。但要注意,在这些代码运行以前不会指定事件处理程序,因此如果这些代码在页面中位于按钮后面,就有可能在一段时间内怎么单击都没有反应。

使用DOM0级方法指定的事件处理程序被认为是元素的方法,因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的this引用当前元素。

        var btn = document.getElementById("myBtn");
        btn.onclick = function() {
            alert(this.id);     // "myBtn"
        };

单击按钮显示的是元素的ID,这个ID是通过this.id取得的。不仅仅是ID,实际上可以在事件处理程序中通过this访问元素的任何属性和方法
以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。

也可以删除通过DOM0级方法指定的事件处理程序。

        btn.onclick = null;    // 删除事件处理程序

此时单击按钮不会有任何动作发生。

个人理解:没有动作发生因为没有事件处理程序,但是的确存在这个事件被触发。只是我们没有对这个事件发生时“做些什么”而已。

如果你使用HTML指定事件处理程序,那么onclick属性的值就是一个包含着在同名HTML特性中指定的代码的函数。而将相应的属性设置为null,也可以删除以这种方式指定的事件e处理程序。


DOM2级事件处理程序

"DOM2级事件"定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener()和removeEventListener()。所有DOM节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。

按钮上为click事件添加事件处理程序:

    <input type="button" id="myBtn" value="Click Me">
        var btn = document.getElementById("myBtn");
        btn.addEventListener("click", function () {
            alert(this.id);
        }, false);

上面的代码为一个按钮添加了onclick事件处理程序,而且该事件会在冒泡阶段被触发(因为最后一个参数false)。与DOM0级方法一样,这里添加的事件处理程序也是在其依附的元素的作用域中运行。使用DOM2级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。

        var btn = document.getElementById("myBtn");
        btn.addEventListener("click", function () {
            alert(this.id);
        }, false);
        btn.addEventListener("click",function() {
            alert("Hello World!");
        }, false)

调用的事件处理程序会按照添加它们的顺序触发,因此首先会显示ID,而后显示”Hello World!“消息。
通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除。

        var btn = document.getElementById("myBtn");
        btn.addEventListener("click", function () {
            alert(this.id);
        }, false);
        btn.removeEventListener("click", function () {
            alert(this.id);
        })

这样行吗?当然不行,两个方法定义的事件处理函数根本就不是”一个东西“,即它们是两个不相干的函数对象。

我们需要使用函数表达式。

        var btn = document.getElementById("myBtn");

        var handler = function() {
            alert(this.id);
        };

        btn.addEventListener("click",handler,false);
        btn.removeEventListener("click",handler,false);

大多数情况,都是将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度的兼容各种浏览器。
不是特别需要,不建议在事件捕获阶段注册事件处理程序。


IE事件处理程序(IE兼容方法)

IE实现了与DOM中类似的两个方法:attachEvent()detachEvent()
这两个方法接受相同的两个参数:事件处理程序名称和事件处理函数。由于IE8及更早版本只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。
使用attachEvent()为按钮添加一个事件处理程序:

        btn.attachEvent("onclick",function() {
            alert("Clicked");
        });

注意,参数从"click"变为了"onclick"。

IE中使用attachEvent()与使用DOM0级方法的主要区别在于事件处理程序的作用域。在使用DOM0级方法的情况下,事件处理程序会在其所属元素的作用域内运行;在使用attachEvent()方法的情况下,事件处理程序会在全局作用域下与西宁,因此this等于window。

        btn.attachEvent("onclick",function() {
            alert(this === window);
        });

在编写跨浏览器的代码时,记住。
与addEventListener()类似,attachEvent()方法也可以用来为一个元素添加多个事件处理程序。

        btn.attachEvent("onclick",function() {
            alert(this === window);
        });
        btn.attachEvent("onclick",function() {
            alert("Hello World!");
        });

但是!这些事件处理程序不是以添加它们的顺序来执行的,而是以相反的顺序被触发
先是看到Hello World!,然后才是true。

使用detachEvent()移除事件处理程序和removeEventListener()是差不多的。

        var btn = document.getElementById("myBtn");

        var handler = function() {
            alert("hello world!")
        }       
        btn.attachEvent("onclick",handler);
        btn.detachEvent("onclick",handler);

支持IE事件处理程序的浏览器有IE和Opera


跨浏览器的事件处理程序

        var EventUtil = {
            addHandler:function(element,type,handler) {
                if (element.addEventListener) {
                    element.addEventListener(type,handler,false);
                } else if (element.attachEvent) {
                    element.attachEvent("on"+typeo,handler);
                } else {
                    element["on"+type] = handler;
                }
            },
            removeHandler:function(element,type,handler) {
                if (elemen.removeEventListener) {
                    element.removeEventListener(type,handler,false);
                } else if (element.detachEvent) {
                    element.detachEvent("on"+type,handler);
                } else {
                    element["on"+type] = null;
                }
            }
        }

这连个方法都会检测传入元素是否存在DOM2级方法。如果存在,使用该方法:传入事件类型、事件处理程序函数和第三个参数false(表示冒泡阶段)。如果存在的是IE的方法,则采取第二种方案。
为了在IE8级更早版本中运行,此时的事件类型必须加上"on"前缀。
最后一种可能是使用DOM0级方法(在现在浏览器中,不会执行这里的代码)。此时,我们使用的是方括号语法来讲属性名指定为事件处理程序,或者将属性设置为null。

        var btn = document.getElementById("myBtn");
        var handler = function() {
            alert("Clicked");
        }

        EventUtil.addHandler(btn,"click",handler);

        EventUtil.removeHandler(btn,"click",handler);

addHandler()和removeHandler()没有考虑所有的浏览器问题,例如在IE中的作用域问题,不过,使用它们添加和移除事件处理程序还是足够了。
此外要注意,DOM0级对每个事件只支持一个事件处理程序。不过现在只支持DOM0级的浏览器应该很少了。


事件对象

在触发DOM上的事件时,会产生一个事件对象event,这个对象中包含所有与事件有关的信息。包活导致事件的元素、事件的类型及其他与特定事件相关的信息。例如,鼠标操作导致的事件对象中,会包含鼠标位置的信息,键盘操作导致的事件对象中,会包含与按下的键有关的信息。
所有的浏览器都支持event对象,但支持方式不同。

DOM中的事件对象

兼容DOM的浏览器会将一个event对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法(DOM0级或DOM2级),都会传入event对象。

        var btn = document.getElementById("myBtn");
        btn.onclick = function(event) {
            alert(event.type);      // "click"
        };
        
        btn.addEventListener("click",function(event) {
            alert(event.type);      // "click<"
        },false);

event属性始终都会包含被触发的事件类型,例如"click"。
在通过HTM特性指定事件处理程序时,变量event中保存着event对象。

    <input type="button" id="myBtn" value="Click Me" onclick="alert(event.type)">

以这种方式提供event对象,可以让HTML特性事件处理程序与JavaScript函数执行相同的操作。
event对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过所有属性都会有一些共有的成员。

        /*
        
        属性/方法                       类型              读/写                 说明

        bubbles                       Boolean            只读             表明事件是否冒泡

        cancelable                    Boolean            只读             表明是否可以取消事件的默认行为

        currentTarget                 Element            只读             其事件处理程序当前正在处理事件的那个元素
        
        defaultPrevented              Boolean            只读             为true表示已经调用了preventDefault()

        detail                        Integer            只读             与事件有关的细节信息

        eventPhase                    Integer            只读             调用事件处理程序的阶段:1捕获阶段、2"处于目标"、3表示冒泡阶段

        preventDefault                Function           只读             取消事件的默认行为。如果cancleable是true,则可以使用这个方法

        stopImmediatePropagation()    Function           只读             取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用

        stopPropagation()             Function           只读             取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法

        target                        Element            只读             事件的目标

        trusted                       Boolean            只读             为true表示目标是浏览器生成的。为false表示事件是由开发人员通过JavaScript创建的

        type                          String             只读             被触发的事件的类型

        view                          AbstractView       只读             与事件关联的抽象视图。等同于发生事件的window对象。

        */

在事件处理程序内部,对象this始终等于cureentTarget的值,而target则只包含事件的实际目标。

        var btn = document.getElementById("myBtn");
        btn.onclick = function(event) {
            alert(event.currentTarget === this);    // true
            alert(event.target === this);           // true
        }

这个例子检测了currentTarget和target与this的值。由于click事件的目标是按钮,因此这三个值是相等的。
如果事件处理程序存在于按钮的父节点中(例如document.body),那么情况就有所变化。

        document.body.onclick = function(event) {
            alert(event.currentTarget === document.body);                   // true
            alert(this === document.body);                                  // true
            alert(event.target === document.getElementById("myBtn"));       // true
        }

当单击这个例子中的按钮时,this和currentTarget都等于document.body,因此事件处理程序是注册到这个元素上的。然而,target元素却等于按钮元素,因为它是click事件真正的目标(用户操作点击了按钮)。只是按钮上没有事件处理程序,没哟函数执行。结果click事件冒泡到了body,在那里事件得到处理。

在需要通过一个函数处理多个事件时,可以使用type属性。

        var btn = document.getElementById("myBtn");
        var handler = function(event) {
            switch(event.type) {
                case "click":
                alert("Clicked");
                break;

                case "mouseover":
                event.target.style.backgroundColor = "red";
                break;

                case "mouseouot":
                event.target.style.backgroundColor = "";
                break;
            }
        };

        btn.onclick = handler;
        btn.onmouseover = handler;
        btn.onmouseout = handler;

例子定义了名为handler的函数,用于处理三种事件:click、mouseover、mouseout。
当单击按钮时,出现一个警告框。当按钮移动到按钮上面时,背景颜色变化成红色。当鼠标移动出按钮时,背景颜色回复默认值。

这里通过检测event.type属性,让函数能够确定发生了什么事件,并执行相应操作。

要阻止特定事件的默认行为,可以使用preventDefault()方法。例如,链接的默认行为就是在被单击时导航到其href指定的URL。如果想阻止链接导航这一默认行为,那么通过onclick事件处理程序可以取消它。

        var link = document.getElementById("myLink");
        link.onclick = function(event) {
            event.preventDefault();
        };

另外只有cancelable属性设置为true的事件,才可以使用preventDefault()来取消去默认行为。
另外,stopPropagation()方法用于立即停止事件在DOM层次中的传播,即取消进一步的事件捕获或冒泡。
例如,直接添加到一个按钮的事件处理程序可以调用stopPropagation(),从而避免触发注册在document.body上面的事件处理程序。

        var btn = document.getElementById("myBtn");
        btn.onclick = function(event) {
            alert("Clicked");
            event.stopPropagation();
        };

        document.body.onclick = function(event) {
            alert("Body clicked");
        }

事件在目标阶段被触发后,不会传播到document.body,因此就不会触发注册在这个元素上的onclick事件处理对象。
事件对象的eventPhase属性,可以用来确定事件当前位于事件流的哪个阶段。

  • 捕获阶段:1
  • 目标阶段:2
  • 冒泡阶段:3
        var btn = document.getElementById("myBtn");
        btn.onclick = function(event) {
            console.log(event.eventPhase);      // 1
        }
        
        document.body.addEventListener("click",function(event) {
            console.log(event.eventPhase);
        },true);                            // 2

        document.body.onclick = function(event) {
            console.log(event.eventPhase);
        };                                  // 3

首先执行的事件处理程序是在捕获阶段触发的添加到document.body的那一个,结果会弹出一个警告框显示表示eventPhase的1。接着,会触发在按钮上注册的事件处理程序,此时的eventPhase值为2。最后一个被触发的事件处理程序,是在冒泡阶段执行的添加到document.body上的那一个,显示eventPhase的值为3。
当eventPhase等于2时,this、target、currentTarget始终相等。

只有事件处理程序执行期间,event对象才会存在;一旦事件处理程序执行完成,event就会销毁。


IE中的事件对象

与访问DOM中的event对象不同,要访问IE中的event对象有几种不同方式,取决与执行事件处理程序的方法。在使用DOM0级方法添加事件处理程序时,event对象作为window对象的一个属性存在。

        var btn = document.getElementById("myBtn");
        btn.onclick = function(event) {
            var event = window.event;
            alert(event.type);      // "click"
        };

在此,我们通过window.event取得了event对象,并检测了被触发事件的类型。
如果使用attachEvent()添加的,那么就会有一个event对象作为参数被传入事件处理程中。

        var btn = document.getElementById("myBtn");
        btn.attachEvent("onclick",function(event) {
            alert(event.type);      // "click"
        });

如果是通过HTML特性指定:

    <input type="button" id="myBtn" value="Click Me" onclick="alert(event.type)">

IE的event对象同样也包含与创建它的事件相关的属性和方法。其中很多属性和方法都有对应的或者相关的DOM属性和方法。与DOM的event对象一样,这些属性和方法也会因为事件类型的不同而不同,但所有事件对象都有这些:

    /*
        
        属性/方法                       类型              读/写                 说明
        
        cancelBubble                   Boolean           读/写            默认值false,将其设置为true可以取消事件冒泡

        returnValue                    Boolean           读/写            默认值为true,将其设置为false可以取消事件的默认行为

        srcElement                     Element           只读             事件的目标

        type                           String            只读             被触发的事件的类型   

    /*

因为事件处理程序的作用域是根据它的方式来确定的,所以不能认为this会始终等于事件目标。故而,最好还是使用event.srcElement比较保险。

        var btn = document.getElementById("myBtn");
        btn.onclick = function(event) {
            alert(window.event.srcElement === this);    // true
        };

        btn.attachEvent("onclick",function(event) {
            alert(event.srcElement === this);           // false
        });

在第一个事件处理程序中(使用DOM0级方法),srcElement属性等于this,但在第二个事件处理程序中,这两者的值不同。
returnValue属性相当于DOM中的preventDefault()方法,它们的作用都是取消给定事件的默认行为,只要将returnValue设置为false,就可以阻止默认行为。

        var link = document.getElementById("myLink");
        link.onclick = function() {
            window.event.returnValue = false;
        };

相应的,cancelBubbles属性与DOM中的stopPropagation()方法相同,都是用来停止事件冒泡的。由于IE不支持事件捕获,因为只能取消事件冒泡;但stopPropagation()可以同时取消事件捕获和冒泡。例如:

        var btn = document.getElementById("myBtn");
        btn.onclick = function() {
            alert("Clicked");
            window.event.cancelBubble = true;
        };

        document.body.onclick = function() {
            alert("Body clicked");
        };

跨浏览器事件对象

虽然DOM和IE中的event对象不同,但基于它们之间的相似性依旧可以拿出跨浏览器的方案来。
IE中event对象的全部信息和方法DOM对象中都有,只不过实现方式不一样。

        var EventUtil = {
            addHandler: function (element, type, handler) {     // 跨浏览器添加事件处理程序
                // 省略
            },

            getEvent: function (event) {                        // 跨浏览器得到事件对象
                return event ? event : window.event;
            },

            getTarget: function (event) {                       // 跨浏览器得到事件目标 
                return event.target || event.srcElement;
            },

            preventDefault: function (event) {                  // 跨浏览器阻止事件传播(IE为取消事件冒泡)
                if (event.preventDefault) {
                    event.preventDefault();
                } else {
                    event.returnValue = false;
                }
            },

            removeHandler: function (element, type, handler) {  // 跨浏览器移除事件处理程序
                // 省略
            },

            stopPropagation: function (event) {                 // 跨浏览器组织默认行为
                if (event.stopPropagation) {
                    event.stopPropagation();
                } else {
                    event.cancelBubble = true;
                }
            }
        };

事件类型

Web浏览器中可能发生的事件有很多类型。不同的事件类型具有不同的信息,而”DOM3及事件“规定了以下几类事件。

  • UI(User Interface)事件,当用户与页面上的元素交互时触发;
  • 焦点事件,当元素获得或失去焦点时触发;
  • 鼠标事件,当用户通过鼠标在页面上执行操作时触发;
  • 滚轮事件,当使用鼠标(或类似设备)时触发;
  • 文本事件,当在文档中输入文本时触发;
  • 键盘事件,当用户通过键盘在页面上执行操作时触发;
  • 合成事件,当为IME(Input Method Editor 输入法编辑器)输入字符时触发;
  • 变动(mutation)事件,当底层DOM结构发生变化时触发。

除了这几类事件之外,HTML5也定义了一组事件,而有些浏览器还会在DOM和BOM中实现其他专有事件。这些专有的事件一般都是根据开发者需求定制的。

UI事件

UI事件指的是那些不一定与用户操作有关的事件。这些事件在DOM规范出现之前,都是以这种或那种形式存在的,而在DOM规范中保留是为了向后兼容。现有的UI事件如下。

  • DOMActivate:表示元素已经被用户操作(通过鼠标或键盘)激活,这个事件在DOM3级事件中被废弃。
  • load:当页面完全加载后在window上面触发,当所有框架都加载完毕时在框架集上面触发,当图像加载完毕时在<Img>元素上面触发,或者当嵌入的内容加载完毕时在<object>元素上面触发。
  • unload:当页面完全卸载后在window上面触发,当所有框架都卸载后在框架集上面触发,或者当嵌入的内容卸载完毕后<object>元素上面触发。
  • abort:在用户停止下载过程时,如果嵌入的内容没有加载完,则在<object>元素上面触发。
  • error:当发生JavaScript错误时在window上面触发,当无法加载图像时在<img>元素上面触发,无法加载嵌入内容时<object>上触发,或者当一或多个框架无法加载时在框架集上触发。
  • select:当用户选择<input>或<textarea>中一或多个字符时被触发。
  • resize:当窗口或框架的大小变化时在window或框架上面触发。
  • scroll:当用户滚动带滚动条的元素中的内容时,在该元素上面触发。<body>元素中包含所加载页面的滚动条。

多数这些事件斗鱼window对象或表单控件相关。

load事件
JavaScript中最常用 的一个事件就是load。当页面完全加载后(包括所有图像、JavaScript文件、CSS文件等外部资源),就会触发window上面的load事件。有两种定义onload事件处理程序的方式。

        EventUtil.addHandler(window,"load",function(event) {
            alert("Loaded!");
        });

这是通过JavaScript来指定事件处理程序的方式,使用了之前跨浏览器的EventUtil对象。同样的,这里也给事件传入了一个event对象。这个event对象中不包括有关这个事件的任何附加信息,但在兼容DOM浏览器中,event.target属性的值会被设置为document,而IE并不会为这个事件设置srcElement属性。

第二种指定onload事件处理程序的方式是为body元素添加一个onload特性。

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
</head>
<title>Load Event Example</title>
<body onload="alert('Loaded!')">
    
</body>
</html>

一般来说,在window上面发生的任何事件都可以在body元素中通过相应的特性来指定,因为在HTML中无法访问window元素。实际上,这只是为了保证向后兼容的一种权宜之计。建议使用JavaScript方式。

图像上面也可以触发load事件。

    <img src="smile.gif" onload="alert('Image loaded.')">

这样当图像加载完毕后会显示一个警告框。同样的,也可以使用JavaScript。

        var img = document.getElementById("myImage");
        EventUtil.addHandler(img,"load",function(event) {
            event = EventUtil.getEvent(event);
            alert(EventUtil.getTarget(event).src);
        })

这里,使用JavaScript指定了onload事件处理程序。同时也传入了event对象,尽管它也不包含什么有用的信息。不过,事件的目标是<img>元素,因此可以通过src属性访问并显示该信息。
在创建新的<img>元素时,可以为其指定一个事件处理程序,以便图像加载完毕后给出提示。此时,最重要的是要在指定src属性之前指定事件。

        EventUtil.addHandler(window,"load",function() {
            var image = document.createElement("img");
            EventUtil.addHandler(image,"load",function(event) {
                event = EventUtil.getEvent(event);
                alert(EventUtil.getTarget(event).src);
            });
            document.body.appendChild(image);
            image.src = "smile.gif";
        });

首先为window指定了onload事件处理程序,原因在于,向DOM中添加一个新元素,所以确保页面已经加载完毕——如果在页面加载前操作document.body会导致错误。然后,创建一个新的图像元素,并设置了其onload事件处理程序。最后又将这个图像添加到页面中,还设置了它的src属性。这里有一点需要注意:新图像元素不一定要从添加到文档后才开始下载,只要设置了src属性就会开始下载。

还有一些元素也以非保准的方式支持load事件。在IE9+,Firefox,opera,chrome,safari3+及更高版本中,<script>元素也支持load事件。以便开发者确定动态加载的JavaScript文件是否加载完毕。只有在设置了<script>元素的src属性并将该元素添加到文档后,才会开始下载JavaScript文件,这和img元素不同。换句话说,对于<script>元素而言,指定src属性和指定事件处理程序的先后顺序不重要了。

        EventUtil.addHandler(window,"load",function() {
            var script = document.createElement("script");
            EventUtil.addHandler(script,"load",function(event) {
                alert("Loaded");
            });
            script.src = "example.js";
            document.body.appendChild(script);
        });

unload事件
与load事件对应的是unload事件,这个事件在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面,就会发生unload事件,而利用这个事件最后情况是清除引用,比避免内存泄漏。

        EventUtil.addHandler(window,"unload",function(event) {
            alert("Unloaded");
        });

此时生成的event对象在兼容DOM的浏览器中只包含target属性(值为document)。ie8及之前版本则为这个事件对象提供了srcElement属性。
指定事件处理程序的第二种方式,也是为body元素添加一个特性。

<body onunload="alert('Unloaded!')">

无论使用哪种方式,都要小心编写onunload事件处理程序中的代码。既然unload事件是在一切都被卸载后触发,那么页面加载后存在的某些对象此时就不一定存在了,此时,操作DOM节点或者元素的样式就会导致错误。

resize事件
当浏览器窗口被调整到一个新的高度或宽度时,就会触发resize事件。这个事件在window上面触发,因此可以通过JavaScript或者body元素中的onresize特性来指定事件处理程序。不过,还是推荐JavaScript方式。

        EventUtil.addHandler(window,"resize",function(event) {
            alert("Resized");
        });

关于何时触发resize事件,不同浏览器有不同机制。IE,Safari,Chrome,Opera会在浏览器变化了1像素时就触发resize事件,然后随着变量不断重复触发。Firefox只会在用户停止调整窗口大小时触发resize事件。由于存在差异,注意不要在这个事件处理程序中加入大计算量代码,因为这些代码有可能被频繁执行,从而导致浏览器反应明显变慢。

浏览器窗口最小化或最大化也会触发resize事件

scroll事件
虽然scroll事件是在window对象上发生的,但他实际表示的是页面中相应元素的变化。在混杂模式下,可以通过<body>元素的scrollLeft和scrollTop来监控这一变化。

        EventUtil.addHandler(window,"scroll",function(event) {
            if (document.compatMode == "CSS1Compat") {
                alert(document.documentElement.scrollTop);
            } else {
                alert(document.body.scrollTop);
            }
        });

以上代码指定的事件处理程序会输出页面的垂直滚动位置——根据呈现模式不同使用了不同的元素。

与resize事件类似,scroll事件也会在文档被滚动期间重复被触发,尽量保持事件处理程序代码简单。


焦点事件

焦点事件会在页面元素获得或失去焦点时触发。利用这些事件并与document.hasFocus()方法及document.activeElement属性配合,可以知晓用户在页面上的行踪。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容