您的位置:首页 > Web前端 > JavaScript

js DOM事件详解

2016-01-28 11:22 555 查看
JavaScript 的事件是以一种流的形式存在的,一个事件会有多个元素同时响应。具体分为:捕获型事件、冒泡型事件(所有浏览器都支持)。

捕获型事件是自上而下的,而冒泡型事件是自下而上的。下面这张图说的很明了:



1.捕获型事件:

<html>

<body>
<div id="div1" style="width:300px;height:300px;background-color:red;">
#div1
<div id="div2" style="width:200px;height:200px;background-color:yellow;margin-top:30px;">
#div2
</div>
</div>

</body>

<script>
var a = document.getElementById('div1');
var b = document.getElementById('div2');
a.addEventListener('click', alertID, true);
b.addEventListener('click', alertID, true);
function alertID() {
alert(this.id);
}

</script>

</html>

当点击#div1(红色区域)时,应该会alert出”div1″当点击#div2(黄色区域)时,应该会先alert出”div1″,再alert出”div2″,因为在事件捕捉阶段,事件是从根元素向下传播的,#div1是#div2的父元素,自然绑定在#div1上的click事件也会先于#div2上的click事件被执行。

2.冒泡型事件:

一个典型的例子:

<html>

<head>

<script type="text/javascript">
var i = 1;
function Add(sText,objText)
{
document.getElementById("Console").innerHTML +=sText + "执行顺序:" + i + "<br />" + "<hr />";
i = i + 1;
//window.event.cancelBubble = true; 
}

</script>

</head>

<body onclick="Add('body事件触发<br />','body')">

<div onclick="Add('div事件触发<br />','div')">
<p onclick="Add('p事件触发<br />','p')" style="background:#c00;">点击</p>

</div>

<div id="Console" style="border:solid 1px #ee0; background:#ffc;"></div>

</body>

</html>

结果:



DOM0事件模型

 

     事件模型在不断发展,早期的事件模型称为DOM0级别。

     DOM0事件模型,所有的浏览器都支持。

     直接在dom对象上注册事件名称,就是DOM0写法,比如:

1 document.getElementById("test").onclick = function(e){};


     意思就是注册一个onclick事件。当然,它和这种写法是一个意思:

1 document.getElementById("test")["onmousemove"] = function(e){};


     这没什么,只不过是两种访问js对象属性的方法,[]的形式主要是为了解决属性名不是合法的标识符,比如:object.123肯定报错,但是object["123"]就避免了这个问题,与此同时,[]的写法,也把js写活了,用字符串表示属性名称,可以在运行时动态绑定事件。

     言归正传,事件被触发时,会默认传入一个参数e
4000
,表示事件对象,通过e,我们可以获取很多有用的信息,比如点击的坐标、具体触发该事件的dom元素等等。

     基于DOM0的事件,对于同一个dom节点而言,只能注册一个,后边注册的同种事件会覆盖之前注册的。例如:

1 var btn = document.getElementById("test");
2
3 btn.onmousemove = function(e){
4   alert("ok");
5 };
6
7 btn["onmousemove"] = function(e){
8   alert("ok1");
9 };


     结果会输出ok1。

     接下来再说说this。事件触发时,this就是指该事件在哪个dom对象上触发。例如:

1 var btn = document.getElementById("test");
2
3 btn.onmousemove = function(e){
4   alert(this.id);
5 };


     结果输出test。因为事件就是在id为test的dom节点上注册的,事件触发时,this当然代表这个dom节点,可以理解为事件是被这个dom节点调用的。

     所以,想解除事件就相当简单了,只需要再注册一次事件,把值设成null,例如:

1 var btn = document.getElementById("test");
2
3 btn.onclick = function(e){
4   alert("ok");
5 };
6
7 btn.onclick = null;


     原理就是最后注册的事件要覆盖之前的,最后一次注册事件设置成null,也就解除了事件绑定。

     事情还没结束,DOM0事件模型还涉及到直接写在html中的事件。例如:

1 <div id="test" class="test" onclick="exec();" ></div>


     通过这种方式注册的事件,同样遵循覆盖原则,同样只能注册一个,最后一个生效。

     区别就是,这样注册的事件,相当于动态调用函数(有点eval的意思),因此不会传入event对象,同时,this指向的是window,不再是触发事件的dom对象。

 

DOM2事件模型

 

     DOM2事件模型相对于DOM0,小菜仅仅了解如下两点:

 

          ·  DOM2支持同一dom元素注册多个同种事件。

          ·  DOM2新增了捕获和冒泡的概念。

 

     DOM2事件通过addEventListener和removeEventListener管理,当然,这是标准。

     但IE8及其以下版本浏览器,自娱自乐,搞出了对应的attachEvent和detachEvent,由于小菜才疏学浅,本文不做讨论。

     addEventListener当然就是注册事件,她有三个参数,分别为:"事件名称", "事件回调", "捕获/冒泡"。举个例子:

1 var btn = document.getElementById("test");
2
3 btn.addEventListener("click", function(e){
4   alert("ok");
5 }, false);


     事件名称就不用多说了,相比DOM0,去掉了前边的on而已。

     事件回调也很好理解,事件触发了总得通知你吧!回调时和DOM0一样,也会默认传入一个event参数,同时this是指触发该事件的dom节点。

     最后一个参数是布尔型,true代表捕获事件,false代表冒泡事件。其实很好理解,先来个示意图:

 


 

     意思就是说,某个元素触发了某个事件,最先得到通知的是window,然后是document,依次而入,直到真正触发事件的那个元素(目标元素)为止,这个过程就是捕获。接下来,事件会从目标元素开始起泡,再依次而出,直到window对象为止,这个过程就是冒泡。

     为什么要这样设计呢?这貌似是由于深厚的历史渊源,小菜也不怎么了解,就不乱说了。

     由此可以看出,捕获事件要比冒泡事件先触发。

     假设有这样的html结构:

1 <div id="test" class="test">
2   <div id="testInner" class="test-inner"></div>
3 </div>


     然后我们在外层div上注册两个click事件,分别是捕获事件和冒泡事件,代码如下:

1 var btn = document.getElementById("test");
2
3 //捕获事件
4 btn.addEventListener("click", function(e){
5   alert("ok1");
6 }, true);
7
8 //冒泡事件
9 btn.addEventListener("click", function(e){
10   alert("ok");
11 }, false);


     最后,点击内层的div,先弹出ok1,后弹出ok。结合上边的原理图,外层div相当于图中的body,内层div相当于图中最下边的div,证明了捕获事件先执行,然后执行冒泡事件。

     为什么要强调点击内层的div呢?因为真正触发事件的dom元素,必须是内层的,外层dom元素才有机会模拟捕获事件和冒泡事件,从原理图上就看出了。

     如果在真正触发事件的dom元素上注册捕获事件和冒泡事件呢?

     html结构同上,js代码如下:

1 var btnInner = document.getElementById("testInner");
2
3 //冒泡事件
4 btnInner.addEventListener("click", function(e){
5   alert("ok");
6 }, false);
7
8 //捕获事件
9 btnInner.addEventListener("click", function(e){
10   alert("ok1");
11 }, true);


     当然还是点击内层div,结果是先弹出ok,再弹出ok1。理论上应该先触发捕获事件,也就是先弹出ok1,但是这里比较特殊,因为我们是在真正触发事件的dom元素上注册的事件,相当于在图中的div上注册,由图可以看出真正触发事件的dom元素,是捕获事件的终点,是冒泡事件的起点,所以这里就不区分事件了,哪个先注册,就先执行哪个。本例中,冒泡事件先注册,所以先执行。

     这个道理适用于多个同种事件,比如说一下子注册了3个冒泡事件,那么执行顺序就按照注册的顺序来,先注册先执行。例如:

1 var btnInner = document.getElementById("testInner");
2
3 btnInner.addEventListener("click", function(e){
4   alert("ok");
5 }, false);
6
7 btnInner.addEventListener("click", function(e){
8   alert("ok1");
9 }, false);
10
11 btnInner.addEventListener("click", function(e){
12   alert("ok2");
13 }, false);


     结果当然是依次弹出ok、ok1、ok2。

     为了进一步理解事件模型,还有一种场景,假如说外层div和内层div同时注册了捕获事件,那么点击内层div时,外层div的事件一定是先触发的,代码如下:

1 var btn = document.getElementById("test");
2 var btnInner = document.getElementById("testInner");
3
4 btnInner.addEventListener("click", function(e){
5   alert("ok");
6 }, true);
7
8 btn.addEventListener("click", function(e){
9   alert("ok1");
10 }, true);


     结果是先弹出ok1。

     假如外层div和内层div都是注册的冒泡事件,点击内层div时,一定是内层div事件先执行,原理相同。

     细心的读者会发现,对于div嵌套的情况,如果点击内层的div,外层的div也会触发事件,这貌似会有问题!

     点击的明明是内层div,但是外层div的事件也触发了,这的确是个问题。

     其实,事件触发时,会默认传入一个event对象,前边提过了,这个event对象上有一个方法:stopPropagation,通过此方法,可以阻止冒泡,这样外层div就接收不到事件了。代码如下:

1 var btn = document.getElementById("test");
2 var btnInner = document.getElementById("testInner");
3
4 btn.addEventListener("click", function(e){
5   alert("ok1");
6 }, false);
7
8 btnInner.addEventListener("click", function(e){
9   //阻止冒泡
10 e.stopPropagation();
11   alert("ok");
12 }, false);


     终于要说说怎么解除事件了。解除事件语法:btn.removeEventListener("事件名称", "事件回调", "捕获/冒泡");

     这和绑定事件的参数一样,详细说明下:

 

          ·  事件名称,就是说解除哪个事件呗。

          ·  事件回调,是一个函数,这个函数必须和注册事件的函数是同一个。

          ·  事件类型,布尔值,这个必须和注册事件时的类型一致。

 

     也就是说,名称、回调、类型,三者共同决定解除哪个事件,缺一不可。举个例子:

1 var btn = document.getElementById("test");
2 //将回调存储在变量中
3 var fn = function(e){
4   alert("ok");
5 };
6 //绑定
7 btn.addEventListener("click", fn, false);
8
9 //解除
10 btn.removeEventListener("click", fn, false);


     要想注册过的事件能够被解除,必须将回调函数保存起来,否则无法解除。

 

DOM0与DOM2混用

 

     事情本来就很乱了,这又来个混合使用,还让不让人活了。。。

     别怕,混合使用完全没问题,DOM0模型和DOM2模型各自遵循自己的规则,互不影响。

     整体上来说,依然是哪个先注册,哪个先执行,其他就没什么了。

 

后记

 


Event 对象

Event 对象代表事件的状态,比如事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态。

事件通常与函数结合使用,函数不会在事件发生前被执行!


事件句柄 (Event Handlers)

HTML 4.0 的新特性之一是能够使 HTML 事件触发浏览器中的行为,比如当用户点击某个 HTML 元素时启动一段 JavaScript。下面是一个属性列表,可将之插入 HTML 标签以定义事件的行为。
属性此事件发生在何时...
onabort图像的加载被中断。
onblur元素失去焦点。
onchange域的内容被改变。
onclick当用户点击某个对象时调用的事件句柄。
ondblclick当用户双击某个对象时调用的事件句柄。
onerror在加载文档或图像时发生错误。
onfocus元素获得焦点。
onkeydown某个键盘按键被按下。
onkeypress某个键盘按键被按下并松开。
onkeyup某个键盘按键被松开。
onload一张页面或一幅图像完成加载。
onmousedown鼠标按钮被按下。
onmousemove鼠标被移动。
onmouseout鼠标从某元素移开。
onmouseover鼠标移到某元素之上。
onmouseup鼠标按键被松开。
onreset重置按钮被点击。
onresize窗口或框架被重新调整大小。
onselect文本被选中。
onsubmit确认按钮被点击。
onunload用户退出页面。


鼠标 / 键盘属性

属性描述
altKey返回当事件被触发时,"ALT" 是否被按下。
button返回当事件被触发时,哪个鼠标按钮被点击。
clientX返回当事件被触发时,鼠标指针的水平坐标。
clientY返回当事件被触发时,鼠标指针的垂直坐标。
ctrlKey返回当事件被触发时,"CTRL" 键是否被按下。
metaKey返回当事件被触发时,"meta" 键是否被按下。
relatedTarget返回与事件的目标节点相关的节点。
screenX返回当某个事件被触发时,鼠标指针的水平坐标。
screenY返回当某个事件被触发时,鼠标指针的垂直坐标。
shiftKey返回当事件被触发时,"SHIFT" 键是否被按下。


IE 属性

除了上面的鼠标/事件属性,IE 浏览器还支持下面的属性:
属性描述
cancelBubble如果事件句柄想阻止事件传播到包容对象,必须把该属性设为 true。
fromElement对于 mouseover 和 mouseout 事件,fromElement 引用移出鼠标的元素。
keyCode对于 keypress 事件,该属性声明了被敲击的键生成的 Unicode 字符码。对于 keydown 和 keyup 事件,它指定了被敲击的键的虚拟键盘码。虚拟键盘码可能和使用的键盘的布局相关。
offsetX,offsetY发生事件的地点在事件源元素的坐标系统中的 x 坐标和 y 坐标。
returnValue如果设置了该属性,它的值比事件句柄的返回值优先级高。把这个属性设置为 fasle,可以取消发生事件的源元素的默认动作。
srcElement对于生成事件的 Window 对象、Document 对象或 Element 对象的引用。
toElement对于 mouseover 和 mouseout 事件,该属性引用移入鼠标的元素。
x,y事件发生的位置的 x 坐标和 y 坐标,它们相对于用CSS动态定位的最内层包容元素。


标准 Event 属性

下面列出了 2 级 DOM 事件标准定义的属性。
属性描述
bubbles返回布尔值,指示事件是否是起泡事件类型。
cancelable返回布尔值,指示事件是否可拥可取消的默认动作。
currentTarget返回其事件监听器触发该事件的元素。
eventPhase返回事件传播的当前阶段。
target返回触发此事件的元素(事件的目标节点)。
timeStamp返回事件生成的日期和时间。
type返回当前 Event 对象表示的事件的名称。


标准 Event 方法

下面列出了 2 级 DOM 事件标准定义的方法。IE 的事件模型不支持这些方法:
方法描述
initEvent()初始化新创建的 Event 对象的属性。
preventDefault()通知浏览器不要执行与事件关联的默认动作。
stopPropagation()不再派发事件。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: