Javascript与HTML之间的交互是通过事件实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。

事件流
事件冒泡

IE的事件流叫做事件冒泡,即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。
所有的现代浏览器都支持事件冒泡。

事件捕获

事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件达到预订目标之前捕获它。
仅在有特殊需要时再使用事件捕获。

DOM事件流

DOM2级事件规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。
即使DOM2级事件规范明确要求捕获阶段不会设计事件目标,但IE9、Safari、Chrome、Firefox和Opera9.5及更高版本都会在捕获阶段触发事件对象上的事件。结果,就是有两个机会在目标对象上面操作事件。


事件流

事件处理程序
HTML事件处理程序
1
<input type="button" value="Click Me" onClick="alert('Clicked')">
1
2
3
4
5
6
<script type="text/javascript">
function showMessage(){
alert('Hello world!');
}
</script>
<input type="button" value="Click Me" onClick="showMessage()">

事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码。
在HTML中指定事件处理程序有2个缺点:

  1. 时差问题:用户可能会在HTML元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。可以使用try-catch来捕获错误:

    1
    <input type="button" value="Click Me" onClick="try{showMessage();}catch(ex){}">
  2. 这样扩展事件处理程序的作用域链在不同浏览器中会导致不同结果。不同javascript引擎遵循的标识符解析规则略有差异,很可能在访问非限定对象成员时出错。

DOM0级事件处理程序
1
2
3
4
5
6
7
var btn = document.getElementById('myBtn');
btn.onclick = function(){
// this指向当前节点
alert(this.id);
};
btn.onclick = null; //删除事件处理程序
DOM2级事件处理程序

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

1
2
3
4
5
6
7
8
9
10
11
12
var btn = document.getElementById('myBtn');
btn.addEventListener('click', function(),{
alert(this.id);
},false);
//如果为同一个节点添加多个事件处理函数,会按照添加它们的顺序触发。所以在此例中,会先显示元素ID再输出“Hello World!"
btn.addEventListener('click', function(){
alert('Hello word!!');
}, false);
//删除一个监听
btn.removeEventListener('click', handleClick, false);

通过addEventListener()添加的事件处理程序只能通过removeEventListener()移除。移除时传入的参数与添加处理程序时使用的参数相同。这也意味着通过addEventListener()添加的匿名函数将无法移除。

IE事件处理程序

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var btn = document.getElementById('myBtn');
var handleClick = function(){
alert(this===window);//输出true;注意!IE处理函数的作用域是全局
};
btn.attachEvent('onclick', handleClick);
//也可以为同一节点添加多个事件处理函数,不过与DOM2方法不同的是,这些事件不是以添加它们的顺序执行,而是以相反的顺序被触发。
//所以在此列中,首先看到的是hello world然后才是id
btn.attachEvent('onclick', function(){
alert('Hello word!');
});
//使用detachEvent移除事件处理程序
btn.detachEvent('onclick', handleClick);

事件对象

在触发DOM上某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息。包括导致事件的元素,事件的类型,以及其他与特定事件相关的信息。

DOM中的事件对象

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

1
2
3
4
5
6
7
var btn = document.getElementById('myBtn');
btn.onclick = function(event){
alert(event.type); //"click"
};
btn.addEventListener('click', function(event){
alert(event.type); //"click"
}, false);

  1. 在事件处理程序内部,对象this始终等于currentTarget的值,target则只包含事件的实际目标。
  2. 要阻止特定事件的默认行为,可以使用preventDefault()方法。只有cancelable属性设置为true的事件,可以使用preventDefault()来取消其默认行为。
  3. stopPropagation()方法用于立即停止事件在DOM层次中的传播,即取消进一步的事件捕获或冒泡。
  4. 事件的eventPhase属性,可以用来确定事件当前正位于事件流的哪个阶段;如果是在捕获阶段调用程序,那么eventPhase等于1;如果事件处理程序处于目标对象上,则eventPhase等于2;如果是在冒泡阶段调用事件处理,则eventPhase等于3。
IE中的事件对象

与访问DOM中的Event对象不同,要访问ie中的event对象有几种不同的方式,取决于指定事件处理程序的方法。

在使用DOM0级方法添加事件处理程序时,event对象作为window对象的一个属性存在。

1
2
3
4
5
var btn = document.getElementById('myBtn');
btn.onclick = function(){
var event = window.event;
alert(event.type); //"click"
};

若事件处理程序是使用attachEvent()添加的,那么就会有一个event对象作为参数被传入事件处理程序函数中。

1
2
3
4
5
var btn = document.getElementById('myBtn');
btn.attachEvent('onclick', function(event){
alert(event.type); // "click"
alert(window.event.type); //也可以通过window.event来访问
});

常用的event属性:

  1. cancelBubble 默认为false,但将其设置为true就可以取消事件冒泡。(与DOM的stopProgagation()方法作用相同)
  2. returnValue 默认为true,但将起设置为false就可以取消事件的默认行为(与DOM的preventDefault()方法作用相同)
  3. srcElement 事件的目标。(与DOM的target属性相同)
常用事件类型
UI事件
  • load
  • unload
  • abort
  • error
  • select
  • resize
  • scroll
焦点事件
  • blur
  • DOMFocusIn
  • DOMFocusOut
  • focus
  • focusin
  • focusout
鼠标与滚轮事件
  • click
  • dblclick
  • mousedown
  • mouseenter
  • mouseleave
  • mousemove
  • mouseout
  • mouseover
  • mouseup
  • mousewheel
  • button
键盘与文本事件
  • keydown
  • keypress
  • keyup
  • textInput
复合事件
  • compositionstart
  • compositionupdate
  • compositionend
变动事件
  • DOMSubtreeModified
  • DOMNodeInserted
  • DOMNodeRemoved
  • DOMNodeInsertedIntoDocument
  • DOMNodeRemovedFromDocument
  • DOMAttrModified
  • DOMCharacterDataModified
HTML5事件
  • contextmenu
  • beforeunload
  • DOMContentLoaded
  • readystatechange
  • pageshow/pagehide
  • hashchange
设备事件
  • orientationchange
  • MozOrientation
  • deviceorientation
  • devicemotion
触摸与手势事件
  • touchstart
  • touchmove
  • touchend
  • touchcancel
  • touches
  • targetTouchs
  • changeTouches
  • gesturestart
  • gesturechange
  • gestureend
事件委托

对”事件处理程序过多“问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

以下面的HTML代码为例:

1
2
3
4
5
<ul id="ul">
<li>aaaaaaaa</li>
<li>bbbbbbbb</li>
<li>cccccccc</li>
</ul>

其中包含3个鼠标移入/移出后执行操作的列表项。按照传统的做法,需要想下面这样为它们添加3个事件处理程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
window.onload = function(){
var oUl = document.getElementById("ul");
var aLi = oUl.getElementsByTagName("li");
for(var i=0; i<aLi.length; i++){
aLi[i].onmouseover = function(){
this.style.background = "red";
}
aLi[i].onmouseout = function(){
this.style.background = "";
}
}
}

如果在复杂的web应用程序中,对所有li都采用这种方式,那么li一多就会有大量的代码用于添加事件处理程序。使用事件委托,只需在DOM树中尽量最高的层次上添加一个事件处理程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
window.onload = function(){
var oUl = document.getElementById("ul");
var aLi = oUl.getElementsByTagName("li");
/*
这里要用到事件源:event 对象,事件源,不管在哪个事件中,只要你操作的那个元素就是事件源。
ie:window.event.srcElement
标准下:event.target
nodeName:找到元素的标签名
*/
oUl.onmouseover = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
//alert(target.innerHTML);
if(target.nodeName.toLowerCase() == "li"){
target.style.background = "red";
}
}
oUl.onmouseout = function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
//alert(target.innerHTML);
if(target.nodeName.toLowerCase() == "li"){
target.style.background = "";
}
}
}

这段代码里,我们使用事件委托只为ul元素添加了一个onclick事件处理程序,只取得了一个DOM元素,只添加了一个事件处理程序,这样占用内存更少,性能更高。
并且当ul内动态添加新的li元素时,事件也可以生效。