JavaScript - 事件总结

markdown

Javascript 和 HTML 之间的交互是通过 事件 来实现的。最早在 IE 中出现,被作为分担服务器端运算负载的一种手段。到现在,几乎所有的浏览器都支持事件处理。在 DOM2 级中得到规范。

事件流

事件流 描述的是从页面中接受事件的顺序。IE 和 Netscape 提出了完全相反的事件流概念。
事件冒泡: IE 中的事件流叫做 事件冒泡 ,即从最具体的元素逐级向外传播到较不具体的元素。(所有现代浏览器都支持事件冒泡)
事件捕获: Netscape 提出了另一种事件流叫做 事件捕获 。他们的思想是最具体的元素应该最后接受到事件,所以事件捕获是 “从外向内” 传播的。
DOM事件流: “DOM2级事件”规定了事件流包括三个阶段:首先发生事件捕获,然后是实际的目标接受到事件,最后一个阶段是事件冒泡。

markdown

汇总: IE8及更早的版本不支持 DOM2 级事件流。

事件处理程序

事件就是用户或浏览器自身执行的某种动作。像clickload等都是事件的名字,而响应这个事件的函数就叫做 事件处理程序 。事件处理程序以 on 开头。Javascript 有三种事件模型:内联模型脚本模型DOM2级模型

内链模型: 被淘汰的方式,举个例子:

1
<input type="button" value="Click me" onclick="alert('Clicked')" />

脚本模型: 这个是现在用的最多的一种方式:

1
2
3
4
5
var btn = document.getElementById('button');
btn.onclick = function(){ //指定事件
alert('Clicked');
}
btn.onclick = null; //删除事件

DOM2级模型

“DOM2级事件”定义了两个方法:(可用于所有 DOM 节点)

  • addEventListener()    添加事件
  • removeEventListener()    移除事件

这两个方法都接受三个参数:要处理的事件名(clickmouseover)、事件处理程序(一个匿名函数) 和 一个布尔值(最后一个布尔值如果是 true 表示在捕获阶段执行事件,如果是 false 表示在冒泡阶段执行事件。)

PS: 大多数情况下,都将事件处理程序添加到事件流的冒泡阶段,这样可以最大限度兼容不同的浏览器。

1
2
3
4
var btn = document.getElementById('button');
btn.addEventListener('click',function(){
alert('Clicked');
},false);

DOM2方式的特点:
1、可以为同一个元素添加多个事件处理程序,他们会按照顺序依次执行。
2、移除事件貌似麻烦了很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var btn = document.getElementById('button');
btn.addEventListener('click',function(){
alert('Clicked');
},false);
btn.removeEventListener('click',function(){
alert('Clicked'); //这样是不能移除事件的
},false); //因为这两个匿名函数已经不是同一个函数了
//必须这样写
var btn = document.getElementById('button');
var handler = function(){
alert('Clicked');
}
btn.addEventListener('click',handler,false);
btn.removeEventListener('click',handler,false); //这样才行

IE中的事件处理程序

IE 实现了与 DOM 中类似的两个方法:

  • attachEvent()    添加事件
  • detachEvent()    移除事件

这两个方法接受两个参数:事件处理程序的名称(onclickonmouseover) 和 事件处理函数(一个匿名函数)。(IE 默认使用冒泡方式)

需要注意的是它 DOM2 中两个方法的区别:
区别一: 第一个参数是 onclick ,而不是 click
区别二: 事件处理程序会在全局作用于下运行,里面的 this 指的是 window 。(在做跨浏览器是这一点要特别注意)

1
2
3
4
var btn = document.getElementById('button');
btn.attachEvent('onclick',function(){
alert(this === window); //true
});

优缺点:
优点:同样可以添加多个事件处理程序。但是,这些事件是按添加的相反顺序执行。
缺点:和上面一样,使用匿名函数是不能被移除。需要传入对相同函数的引用。

跨浏览器事件处理程序

我们要创建一个方法,视情况分别使用 传统模型DOM2级模型 或者 IE 事件处理程序。
这个方法接受三个参数:要操作的元素、事件类型 和 事件处理函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//添加事件
function addEvent(element,type,fn){
if(element.addEventListener){
element.addEventListener(type,fn,false);
}else if(element.attachEvent){
element.attachEvent('on'+type,fn);
}else{
element['on'+type] = fn;
}
}
//移除事件
function removeEvent(element,type,fn){
if(element.removeEventListener){
element.removeEventListener(type,fn,false);
}else if(element.detachEvent){
element.detachEvent('on'+type,fn);
}else{
element['on'+type] = null;
}
}

事件对象

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

W3C中的事件对象

无论指定事件处理程序时使用什么方法(DOM0级、DOM2级等),都会传入 event 对象。

1
2
3
4
btn = document.getElementById('button');
addEvent(btn,'click',function(event){
alert(event.type); //click
});

event 对象的属性和方法: (这里之列举最常用的)

属性/方法 说明
target 事件的目标
type 被触发的事件类型
currentTarget 其事件处理程序当前正在处理事件的那个元素
cancelable 表明是否可以取消当前事件的默认行为
preventDefault() 取消时间的默认行为。如果 cancelable 是 true 就可以使用这个方法
bubbles 表明事件是否冒泡
stopPropagation() 取消时间的进一步捕获或冒泡。如果 bubbles 为 true 则可以使用这个方法

注意:
1、在事件执行内部, this 始终等于 currentTarget 。而 target 只包含事件的实际目标。也就是说 thiscurrentTarget 返回的是你为事件指定的目标元素,而 target 是真正触发这个事件的目标。
2、只有 cancelable 的值为 true 的事件,才可以使用 preventDefault() 来取消器默认行为。

1
2
3
4
var link = document.getElementById('myLink');
link.onclick = function(event){
event.preventDefault(); //点击链接不会跳转
}

3、stopPropagation() 方法用于立即停止事件冒泡或捕获。

1
2
3
4
5
6
7
8
var btn = document.getElementById('button');
btn.onclick = function(event){
alert('Clicked');
event.stopPropagation(); //如果不阻止事件的传播,点击 btn 后下面的事件也会执行
}
document.body.onclick = function(){
alert('Body Element');
}

IE中的事件对象

与访问 W3C 中的 event 对象不同,IE 中的 event 对象的访问 取决于指定事件处理程序的方法
脚本模式: event 作为 window 对象的一个属性存在

1
2
3
4
btn.onclick = function(){
var event = window.event;
alert(event.type); //click
}

DOM2级: event 作为一个参数被传入事件处理程序。

1
2
3
btn.attachEvent('onlick',function(event){
alert(event.type); //click
});

IE 中的 event 对象的属性和方法:

属性/方法 说明
cancelBubble 默认值为 false,将其设置为 true 时可以取消事件冒泡(相当于 DOM 中的 stopPropagation() 方法)
returnValue 默认值为 true,将其设置为 false 是可以取消时间的默认行为(相当于 DOM 中的 preventDefault() 方法)
srcElement 事件的目标(和 DOM 中的 target 一样)
type 被触发事件的类型

注意:
1、因为事件处理程序的 作用域 是由指定的方式决定的,所以要特别注意

1
2
3
4
5
6
btn.onclick = function(){
alert(window.event.srcElement === this); //true
} //这里的this指的是btnDOM
btn.attachEvent('onclick',function(event){
alert(event.srcElement === this); //false
}); //因为这里的this指的是window

2、只要将 returnValue 属性的值设置为 false 就可以阻止事件的默认行为

1
2
3
4
var link = document.getElementById('myLink');
link.onclick = function(){
window.event.returnValue = fasle; //链接不会跳转
}

3、cancelBubble 属性用来停止事件的冒泡(IE不支持事件捕获)这里就不举例了。

跨浏览器的事件对象

这里有4个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//获取对象
function getEvent(event){
return event ? event : window.event;
}
//获取事件目标元素target
function getTarget(event){
return event.target || event.srcElement;
}
//阻止默认行为
function preventDefault(event){
if(event.preventDefault){
event.preventDefault();
}else if(event.returnValue){
event.returnValue = false;
}
}
//阻止冒泡
function stopPropagation(event){
if(event.stopPropagation){
event.stopPropagation();
}else if(event.cancelable){
event.cancelable = true;
}
}

常用事件类型汇总

常用的事件类型和具体用法你都可以在这里找到:HTML DOM 事件对象
自己介绍一下 鼠标事件键盘事件

鼠标事件

当浏览器执行鼠标事件的时候回以函数的形式返回一个 event 对象
event对象有一个 button 属相,按下鼠标的不同按键会返回不同的值(貌似很有用)

1
2
3
4
5
6
7
8
9
10
document.onclick = function(event){
event = event || window.event;
alert(event.button); //chrome 左键弹出0 其它不支持;FF 左键0 中键1 右键2;
//IE 左键0 中键1 右键不支持
}
//尝试一次别的事件
document.onmouseup = function(event){
event = event || window.event;
alert(event.button); //chrome 、IE 和 Firefox都符合W3C标准,做到了兼容
}

键盘事件

键盘事件一共有三种:
keydown    键盘按下是执行
keyup    键盘松开是执行
keypress    只支持字符键(能打印出字符的键,不包括enter、shift、Ctrl等)
这里理解一个概念
键码: 键盘上每个键都会对应一个数字,这个数字就称为键码。(字符的键码就是其对应的ASICC编码(字母的话按小写来),非字符也会有一个对应的值)。怎么获得这个键码呢??用到event对象的另一个属性 keyCode

1
2
3
4
document.onkeydown = function(event){
event = event || window.event;
alert(event.keyCode); //这时按下键盘上的每个键都会返回对应的键码
}

如果使用keypress方法来返回keyCode呢?

1
2
3
4
5
document.onkeypress = function(event){
event = event || window.event;
alert(event.keyCode); //Chrome和IE 非字符键不返回,字符键返回对应ASCII编码,并且区分大小写
//forefox 非字符键不返回,字符键全部返回 0
}

注意:键码在不同浏览器上可能会有所不同(比如 ; 分号,,可以自己试一下)。那么怎么做到兼容呢??再来认识一个概念:
字符编码: 这个是唯一的

1
2
3
4
document.onkeypress = function(event){
event = event || window.event;
alert(event.charCode); //返回对应的字符编码,(;分号可以兼容了)
}

如果只用到字符的事件就用 keypress 的 charCode方法

事件委托

由于事件处理程序可以为现代 WEB 应用程序提供交互能力,因此许多开发人员会不分青红皂白地想页面中添加大量的处理程序。这样做会导致什么问题呢?首先,每个函数都是对象,都会占用内存;其次,必须事件指定好所有事件处理程序而导致 DOM 访问次数过多,延迟整个页面的交互就绪时间。
对“事件处理程序过多”问题的解决方案就是 事件委托 。事件委托利用 事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
举个例子: 下面是三个不同的按钮。

1
2
3
4
5
<div id="box">
<button id="btn1">这是按钮A</button>
<button id="btn2">这是按钮B</button>
<button id="btn3">这是按钮C</button>
</div>

我们要为他们指定 click 事件,传统意义上我们会分别为他们指定事件,就像下面这样做:

1
2
3
4
5
6
7
8
9
10
11
12
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
var btn3 = document.getElementById('btn3');
btn1.click = function(){
alert('这是按钮A!');
}
btn2.click = function(){
alert('这是按钮B!');
}
btn3.click = function(){
alert('这是按钮C!');
}

在来看看用 事件委托 应该怎么做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
addEvent(document,'click',function(event){
var event = getEvent(event);
var target = getTarget(event);
switch(target.id){
case 'btn1':
alert('这是按钮A!');
break;
case 'btn2':
alert('这是按钮B!');
break;
case 'btn3':
alert('这是按钮C!');
break;
}
});

对比就会发现这种方式的优势,首先,我们只指定了一个事件就完成了为三个按钮添加事件的功能,较少了程序运行所需的内存;另外,只有一次 DOM 操作,这使得交互会很快就绪。是不是很完美。

适合采用 事件委托 的事件包括 clickmousedownmouseupkeydownkeyupkeypress。(必须支持事件冒泡)