什么是事件

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

事件流

事件流描述的是从页面接受事件的顺序。


1、事件冒泡

事件开始时由最具体的元素接收,然后逐渐向上传播到较为不具体的节点(文档)
在这里插入图片描述

2、事件捕获

事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意是到达预定目标之前捕获它。
在这里插入图片描述

3、DOM事件流

“DOM2级事件”规定的时间流包括三个阶段: 事件捕获阶段、处于目标的阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段。
在这里插入图片描述


事件处理程序

1、HTML事件处理程序

1
2
<input type="button" value = "点击" onclick = "alert('阿清呀')" />
<input type="button" value = "点击" onclick = function() />

事件处理程序中的代码在执行时,有权访问全局作用域中的任何代码。

缺点:

  1. 存在一个时差问题。因为用户可能会在HTML元素一出现在页面上就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件。
  2. 另一个缺点是,这样扩展事件处理程序的作用域链在不同浏览器中会导致不同结果。不同 Javascript引擎遵循的标识符解析规则略有差异,很可能会在访问非限定对象成员时出错。

2、DOM0 级事件处理程序

JavaScript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。
优点:简单、具有跨浏览器的优势。
缺点:无法为一个元素绑定多个事件。

1
2
3
4
var btn = document.getElementById("Btn");
btn.onclick = function() {
alert(this.id); //"Btn"
};

在事件处理程序中可以通过this访问元素的任何属性和方法。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。this指的绑定事件的元素。

1
btn.onclick = null;

将相应的属性设置为null,也可以删除以这种方式制定的事件处理程序。


3、DOM2 级事件处理程序

“DOM2级事件”定义了两个方法,用于处理指定和删除事件处理程序的操作: addEventlistener()和 removeEventlistener()。
参数:

  1. 事件的字符串,不要on
  2. 回调函数,当事件触发时该函数会被调用
  3. 这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序,一般都传false。

使用DOM2 级事件的好处是可以添加多个事件处理程序,事件的处理顺序是按照添它们的顺序触发的。

1
2
3
4
var btn = document.getElementById("btn");
btn.addEventListener("click", function() {
alert(this); //[object HTMLButtonElement]
}, false);

DOM2 级事件处理程序中的 this 是指向它绑定的元素。

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

1
2
3
4
5
6
7
8
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function() {
alert(this.id);
}, false);
// 这里省略了??代码
btn.removeEventListener("click", function() { // 没有用!
alert(this.id);
}, false);
1
2
3
4
5
6
7
var btn = document.getElementById("myBtn");
var handler = function() {
alert(this.id);
};
btn.addEventListener("click", handler, false);
// 这里省略了??代码
btn.removeEventListener("click", handler, false); // 有效!

4、IE事件处理程序

在IE8中实现了与DOM中类似的两个方法: attachEvent()和 detachEvent()。来绑定事件

参数:

  1. 事件的字符串,要on。
  2. 回调函数。

这个方法也可以同时为一个事件绑定多个处理函数。不同的是它是后绑定的先执行,执行顺序和addEventListener()相反。

在IE中使用attachEvent() 与其他两个事件处理程序的主要区别就是作用域,在DOM 0 级和DOM 2 级的情况下,事件处理程序会在所谓的元素作用域中运行。在使用attachEvent() 方法的情况下事件处理程序会在全局作用域中运行。因此this等于window,这在编写跨浏览器作用域中非常重要。

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

使用attachEvent ()添加的事件可以通过detachEvent ()来移除,条件是必须提供相同的参数。与DOM方法一样,这也意味着添加的匿名函数将不能被移除。不过,只要能够将对相同函数的引用传
给detachEvent(),就可以移除相应的事件处理程序。


5、跨浏览器事件处理程序

对其进行封装。

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
var EventUtil = {
addHandler: function(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},

// 解决浏览器兼容性问题
getEvent: function(event) {
return event ? event : window.event;
},

// 返回事件目标
getTarget: function(event) {
return event.target || event.srcElement;
},

//取消事件的默认行为。
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},

// 移除事件处理程序
removeHandler: function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},

//提供相关元素的信息,relatedTarget这个属性只对与mouseover和mouseout事件才包含值。
getRelatedTarget: function(event) {
if (event.relatedTarget) {
return event.relatedTarget;
} else if (event.toElement) {
return event.toElement;
} else if (event.fromElement) {
return event.fromElement;
} else {
return null;
}
},

//获取鼠标按钮
/**
* 0: 表示没有按下按钮。
1: 表示按下了主鼠标按钮。
2: 表示按下了次鼠标按钮。
3: 表示同时按下了主、次鼠标按钮
4: 表示按下了中间的鼠标按钮。
5: 表示同时按下了主鼠标按钮和中间的鼠标按钮。
6: 表示同时按下了次鼠标按钮和中间的鼠标按钮。
7: 表示同时按下了三个鼠标按钮。*/
getButton: function(event) {
if (document.implementation.hasFeature("MouseEvents", "2.0")) {
return event.button;
} else {
switch (event.button) {
case 0:
case 1:
case 3:
case 5:
case 7:
return 0;
case 2:
case 6:
return 2;
case 4:
return 1;
}
}
},

//获得鼠标滚轮的增量值delta
getWheelDelta: function(event) {
if (event.wheelDelta) {
return (client.engine.opera && client.engine.opera < 9.5 ?
-event.wheelDelta : event.wheelDelta);
} else {
return -event.detail * 40;
}
},


//获取字符串编码 主要用于文本框 监听事件是keypress
getCharCode: function(event) {
if (typeof event.charCode == "number") {
return event.charCode;
} else {
return event.keyCode;
}
},

//取消进一步的事件捕获或者冒泡
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};


事件对象

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

1、DOM中的事件对象

兼容DOM的浏览器会将一个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);

event.type 属性表示的事件类型,始终包含着被触发的事件。

event包含于创建它的特定事件有关的属性和方法,触发的事件类型不一样,可用的属性和方法也不一样。不过所有事件都会有下列成员:
在这里插入图片描述在这里插入图片描述
在事件处理程序的内部,对象 this 始终等于 currentTarget 的值,而 target 则包含事件的事件的实际目标。两种情况:

1、如果直接将事件处理程序给定目标元素,则this 、currentTarget、target 三个值相等。

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

2、如果将事件处理程序给定目标元素的父节点或者祖先节点那么target 的值就不和this 、currentTarget 的值相等。(事件的委派中要注意)

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

preventDefault() 方法,可以阻止特定事件的默认行为。如链接的默认行为是点击时会跳转到指定的URl。如果阻止它的的默认行为,就会点击时没有反应。cancelable 属性设置为 true的事件,才可以用 proventDefault() 来取消其默认行为。
1
2
3
4
var link = document.getElementById("myLink");
link.onclick = function(event) {
event.preventDefault();
};

stopPropagation() 方法 用于立即停止事件在DOM中的传播,取消事件的进一步捕获或者冒泡。例如 在一个按钮的事件处理程序中调用 stopPropagation(),这样冒泡就不会在document.body上响应处理事件。
1
2
3
4
5
6
7
8
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
alert("Clicked");
event.stopPropagation();
};
document.body.onclick = function(event){
alert("Body clicked"); //不会触发该事件
};

eventPhase属性,可以用来确定事件当前正位于事件流的哪个阶段。

  • event.eventPhase 等于 1 :在捕获阶段调用处理事件。
  • event.eventPhase 等于 2 :事件处理程序在目标对象上。
  • event.eventPhase 等于 3 :在冒泡阶段调用处理程序。

尽管“处于目标”发生在冒泡阶段,但 event.eventPhase 是等于 2 的。
当 event.eventPhase 等于 2 的时候,this 、currentTarget、target 三个值相等。

1
2
3
4
5
6
7
8
9
10
var btn = document.getElementById("myBtn");
btn.onclick = function(event) {
alert(event.eventPhase); //2
};
document.body.addEventListener("click", function(event) {
alert(event.eventPhase); //1
}, true);
document.body.onclick = function(event) {
alert(event.eventPhase); //3
};

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


2、IE中的事件对象

要访问 IE 中的 event 对象有几种方式,取决于指定事件处理的方法。

  1. 如果使用DOM 0 级方法添加事件处理程序时event对象作为window对象的一个属性存在。
1
2
3
4
5
var btn = document.getElementById("myBtn");
btn.onclick = function () {
var event = window.event;
alert(event.type); //"click"
};
  1. 如果使用attachEvent() 添加的事件,一个名叫 event 对象作为参数被传入事件函数中。
1
2
3
4
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function (event) {
alert(event.type); //"click"
});
  1. 如果通过HTML特性指定的事件处理程序,可以通过一个名叫 event 的变量来访问 event 对象。和第二种相同。
1
<input type="button" value="Click Me" onclick="alert(event.type)">

IE的 event 对象同样也包含与创建它的事件相关的属性和方法。其中很多属性和方法都有对应的
或者相关的DOM属性和方法。与DOM的event对象一样,这些属性和方法也会因为事件类型的不同
而不同,但所有事件对象都会包含下表所列的属性和方法。
在这里插入图片描述

event.srcElement 事件的目标。因为事件处理的作用域是根据指定它的方式来确定的,所以不能认为 this 会始终等于事件的目标,使用event.srcEvent 比较好。(跨浏览器时候要注意)

1
2
3
4
5
6
7
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert(window.event.srcElement === this); //true
};
btn.attachEvent("onclick", function(event) {
alert(event.srcElement === this); //false
});

returnValue 属性相当于DOM中的preventDefault()方法,它们的作用都是取消给定事件的默认行为。只要将returnValue设置为false,就可以阻止默认行为。

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

cancelBubble 属性与DOM中的stopPropagation()方法作用相同,都是用来停止事件冒泡的。由于IE不支持事件捕获,所以只能取消事件冒泡;但stopPropagatioin ()可以同时取消事件捕获和冒泡。

1
2
3
4
5
6
7
8
var btn = document.getElementById("myBtn");
btn.onclick = function() {
alert("Clicked");
window.event.cancelBubble = true;
};
document.body.onclick = function() {
alert("Body clicked"); // 无法响应事件
};


事件类型

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

1、UI事件

UI事件指的是那些不一定与用户操作有关的事件。

  • load:当页面完全加载后在window上面触发,当所有框架都加载完毕时在框架集上面触发,当图像加载载完毕时在<img>元素上面触发,或者当嵌人的内容加载完毕时在<object>元素上面触发。
  • unload:当页面完全卸载后在window.上面触发,当所有框架都卸载后在框架集上面触发,或者当嵌人的内容卸载完毕后在<object>元素上面触发。
  • abort:在用户停止下载过程时,如果嵌人的内容没有加载完,则在<object>元素上面触发。
  • error :当发生JavaScript错误时在window上面触发,当无法加载图像时在< img>元素上面触发,当无法加载嵌人内容时在<object>元素上面触发,或者当有一或多个框架无法加载时在框架集上面触发。
  • select:当用户选择文本框(<input>或<texterea> )中的一或多个字符时触发。
  • resize:当窗口或框架的大小变化时在window或框架上面触发。
  • scroll:当用户滚动带滚动条的元素中的内容时,在该元素上面触发。<body>元素中包含所加 载页面的滚动条。

1、load事件

JavaScript中最常用的一个事件就是 load。 当页面完全加载后(包括所有图像、JavaScript 文件、CSS文件等外部资源),就会触发window.上面的load事件。

1
2
3
EventUtil.addHandler(load,"window",function(event){
alert("aqingya");
});

图像也可以触发load事件。

1
2
3
4
5
var image = document.getElementById("myImage");
EventUtil.addHandler(image, "load", function (event) {
event = EventUtil.getEvent(event);
alert(EventUtil.getTarget(event).src);
});

在创建新的<img>元素时,可以为其指定一一个事件处理程序,以便图像加载完毕后给出提示。此时,最重要的是要在指定src属性之前先指定事件,举个栗子:

1
2
3
4
5
6
7
8
9
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 属性就会开始下载。

2、unload 事件

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

注意:unload事件是在一切都被卸载之后才触发,那么在页面加载后存在的那些对象,此时就不一定存在了。此时,操作DOM节点或者元素的样式就会导致错误。

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

3、resize 事件

当浏览器窗口调整到一个新高度时,就会触发 resize 事件,这个事件是在window上触发的。

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

resize事件会频繁的触发,所以尽量保持事件中代码的简单。


4、scroll 事件

虽然 scroll 事件是在 window 对象上发生的,但它实际表示的则是页面中相应元素的变化。在混杂模式下,可以通过<body>元素的scrollLeft和scrol1Top来监控到这- -变化;而在标准模式下,除Safari之外的所有浏览器都会通过<html>元素来反映这一变化.

1
2
3
4
5
6
7
EventUtil.addHandler(window, "scroll", function (event) {
if (document.compatMode == "CSS1Compat") {
alert(document.documentElement.scrollTop);
} else {
alert(document.body.scrollTop);
}
});

scroll事件在文档滚动期间会频繁的被触发,所以尽量保持事件中代码的简单。



2、 焦点事件

焦点事件会在页面元素获得或失去焦点时触发。利用这些事件并与document . hasFocus()方法及document . activeEl ement属性配合,有以下6个焦点事件。

  • blur:在元素失去焦点时触发。这个事件不会冒泡;所有浏览器都支持它。
  • DOMFoCusIn:在元素获得焦点时触发。这个事件与HTML事件focus等价,但它冒泡。只有Opera支持这个事件。DOM3级事件废弃了DOMFocusIn,选择了focusin。
  • DOMFocusOut :在元素失去焦点时触发。这个事件是HTML事件blur的通用版本。只有Opera支持这个事件。DOM3级事件废弃了DOMFocusOut,选择了focusout。
  • focus :在元素获得焦点时触发。这个事件不会冒泡;所有浏览器都支持它。
  • focusin:在元素获得焦点时触发。这个事件与HTML事件focus等价,但它冒泡。支持这个事件的浏览器有IE5.5+、Safari 5.1+、Opera 11.5+和Chrome。
  • focusout:在元素失去焦点时触发。这个事件是HTML事件blur的通用版本。支持这个事件的浏览器有IE5.5+、Safari 5.1+、Opera 11.5+和Chrome。


3、鼠标与滚轮事件

DOM 3 级事件中定义了11个鼠标事件

  • click:在用户单击主鼠标按钮(一般是左边的按钮) 或者按下回车键时触发。 这一点对确保易访问性很重要,意味着onclick事件处理程序既可以通过键盘也可以通过鼠标执行。
  • dblclick:在用户双击主鼠标按钮(一般是左边的按钮) 时触发。从技术上说,这个事 件并不是DOM2级事件规范中规定的,但鉴于它得到了广泛支持,所以DOM3级事件将其纳人了标准。
  • mousedown:在用户按下了任意鼠标按钮时触发。不能通过键盘触发这个事件。
  • mouseenter :在鼠标光标从元素外部首次移动到元素范围之内时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。DOM2 级事件并没有定义这个事件,但DOM3级事件将它纳入了规范。
  • mouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。DOM2 级事件并没有定义这个事件,但DOM3级事件将它纳人了规范。
  • mousemove:当鼠标指针在元素内部移动时重复地触发。不能通过键盘触发这个事件。
  • mouseout:在鼠标指针位于一个元素上方,然后用户将其移人另一个元素时触发。又移人的另一个元素可能位于前一个元素的外部,也可能是这个元素的子元素。不能通过键盘触发这个事件。
  • mouseover :在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触发。不能通过键盘触发这个事件。
  • mouseup:在用户释放鼠标按钮时触发。不能通过键盘触发这个事件。
  • mouseover:事件会在鼠标指针移动到指定的对象上时发生。
  • mouseout :事件会在鼠标指针移出指定的对象时发生。

页面上的所有元素都支持鼠标事件。除了mouseenter和mouseleave,所有鼠标事件都会冒泡,也可以被取消,而取消鼠标事件将会影响浏览器的默认行为。取消鼠标事件的默认行为还会影响其他事件,因为鼠标事件与其他事件是密不可分的关系。

1、客户区坐标的位置

clientX和clientY属性 他们的值是保存事件触发时鼠标指针在视口中的水平和垂直坐标。

1
2
3
4
5
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function (event) {
event = EventUtil.getEvent(event);
alert("Client coordinates: " + event.clientX + "," + event.clientY);
});

在这里插入图片描述

2、页面坐标位置

pageX和pageY属性,他们的值是保存事件触发时鼠标指针在页面中的水平和垂直坐标。

1
2
3
4
5
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function (event) {
event = EventUtil.getEvent(event);
alert("Page coordinates: " + event.pageX + "," + event.pageY);
})

在IE 8之前不支持事件对象上的页面坐标。不会可以使用客户区坐标和滚动信息计算出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function (event) {
event = EventUtil.getEvent(event);
var pageX = event.pageX,
pageY = event.pageY;
if (pageX === undefined) {
pageX = event.clientX + (document.body.scrollLeft ||
document.documentElement.scrollLeft);
}
if (pageY === undefined) {
pageY = event.clientY + (document.body.scrollTop ||
document.documentElement.scrollTop);
}
alert("Page coordinates: " + pageX + "," + pageY);
});


3、屏幕坐标位置

screenX和screenY属性,他们的值是保存事件触发时鼠标指针在整个屏幕中的水平和垂直坐标。
在这里插入图片描述

1
2
3
4
5
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function (event) {
event = EventUtil.getEvent(event);
alert("Screen coordinates: " + event.screenX + "," + event.screenY);
});

4、修改键

DOM为此规定了4个属性,表示这些修改键的状态: shiftkey、 ctrlKey、altKey和metaKey(IE8之前不支持)分别对应shift键、ctrl键、alt键、window键。这些属性中包含的都是布尔值,如果相应的键被按下了,则值为true,否则值为false。 例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var div = document.getElementById("myDiv");
EventUtil.addHandler(div, "click", function (event) {
event = EventUtil.getEvent(event);
var keys = new Array();
if (event.shiftKey) {
keys.push("shift");
}
if (event.ctrlKey) {
keys.push("ctrl");
}
if (event.altKey) {
keys.push("alt");
}
if (event.metaKey) {
keys.push("meta");
}
alert("Keys: " + keys.join(","));
});

5、detail

“DOM2级事件”规范在event对象中还提供了detail属性,对于鼠标事件来说,detail 中包含了一一个数值,表示在给定位置上发生了多少次单击。在同-一个元素上相继地发生一次mousedown 和次mouseup 事件算作一次单击。detail属性从1开始计数,每次单击发生后都会递增。如果鼠标在mousedown和mouseup之间移动了位置,则detail会被重置为0。

6、鼠标滚轮事件
mousewheel 事件,当用户通过鼠标滚轮与页面交互、在垂直方向上滚动页面时(无论向上还是向下),就会触发 mousewheel 事件。

将mousewheel事件处理程序指定给页面中的任何元素或document对象,即可处理鼠标滚轮的交互操作。例子:

1
2
3
4
EventUtil.addHandler(document, "mousewheel", function (event) {
event = EventUtil.getEvent(event);
alert(event.wheelDelta); //
});

Firefox支持一-个名为DOMMouseScroll的类似事件,也是在鼠标滚轮滚动时触发。滚轮的信息则保存在detail属性中,当向前滚动鼠标滚轮时,这个属性的值是-3的倍数,当向后滚动鼠标滚轮时,这个属性的值是3的倍数。


4、键盘与文本事件

  • keydown:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。
  • keypress:当用户按下键盘上的字符键时触发,而且如果按住不放的话,会重复触发此事件。按下Esc键也会触发这个事件。
  • keyup:当用户释放键盘上的键时触发。

键盘事件的事件对象中也有shiftKey、 ctrlKey、 altKey 和 metaKey属性(IE不支持)。


1、键码

keyCode属性:对于 keypress 事件,该属性声明了被敲击的键生成的 Unicode 字符码。对于 keydown 和 keyup 事件,它指定了被敲击的键的虚拟键盘码。虚拟键盘码可能和使用的键盘的布局相关。注意:数字键和小键盘的数字键 字符码不一样。

key属性:key属性是为了取代keyCode而新增的,它的值是-一个字符串。在按下某个字符键时,key的值就是相应的文本字符(如“k”或“M””);在按下非字符键时,key 的值是相应键的名(如“ Shift”)。而char属性在按下字符键时的行为与key相同,但在按下非字符键时值为null。

2、textInput 事件

textInput:根据规范,当用户在可编辑区域中输人字符时,就会触发这个事件。这个用于替代keypress的textInput事件的行为稍有不同。区别之一就是任何可以获得焦点的元素都可以触发keypress事件,但只有可编辑区域才能触发textInput事件。区别之二是textInput事件只会在用户按下能够输人实际字符的键时才会被触发,而keypress事件则在按下那些能够影响文本显示的键时也会触发(例如退格键)。

由于textInput事件主要考虑的是字符,因此它的event对象中还包含一个data属性,这个属性的值就是用户输人的字符(而非字符编码)。换句话说,用户在没有按上档键的情况下按下了S键,data的值就是”s”,而如果在按住上档键时按下该键,data的值就是”S”。

1
2
3
4
5
var textbox = document.getElementById("myText");
EventUtil.addHandler(textbox, "textInput", function (event) {
event = EventUtil.getEvent(event);
alert(event.data);
});

event对象上还有一个属性,叫inputMethod, 表示把文本输人到文本框中的方式。

  • 0,表示浏览器不确定是怎么输入的。
  • 1,表示是使用键盘输人的
  • 2,表示文本是粘贴进来的。
  • 3,表示文本是拖放进来的。
  • 4,表示文本是使用IME输入的。
  • 5,表示文本是通过在表单中选择某一项输入的。
  • 6,表示文本是通过手写输入的(比如使用手写笔)。
  • 7,表示文本是通过语音输入的。
  • 8,表示文本是通过几种方法组合输入的。
  • 9,表示文本是通过脚本输入的。


5、复合事件

  • compositionstart: 在IME的文本复合系统打开时触发,表示要开始输人了。
  • compositionupdate:在向输人字段中插入新字符时触发。
  • compositionend:在IME的文本复合系统关闭时触发,表示返回正常键盘输人状态。

复合事件与文本事件在很多方面都很相似。在触发复合事件时,目标是接收文本的输人字段。但它比文本事件的事件对象多一个属性 data,其中包含以下几个值中的一个:

  • 如果在compositionstart事件发生时访问,包含正在编辑的文本(例如,已经选中的需要马上替换的文本);
  • 如果在composi tionupdate事件发生时访问,包含正插人的新字符;
  • 如果在compositionend事件发生时访问,包含此次输人会话中插人的所有字符。


6、变动事件

  • DOM2级的变动( mutation )事件能在DOM中的某一部分 发生变化时给出提示。变动事件是为 XMLHTML DOM设计的,并不特定于某种语言。DOM2级定义了如下变动事件。
  • DOMSubtreeModified:在DOM结构中发生任何变化时触发。这个事件在其他任何事件触发后都会触发。
  • DOMNodeInserted:在一个节 点作为子节点被插人到另-一 个节点中时触发。
  • DOMNodeRemoved:在节点从其父节点中被移除时触发。
  • DOMNodeInsertedIntoDocument:在一个节点被直接插入文档或通过子树间接插人文档之后触发。这个事件在DOMNodeInserted之后触发。
  • DOMNodeRemovedF romDocument:在-个节 点被直接从文档中移除或通过 子树间接从文档中移除之前触发。这个事件在DOMNodeRemoved之后触发。
  • DOMAttrModi fied:在特性被修改之后触发。
  • DOMCharacterDataModified:在文本节点的值发生变化时触发。

检测浏览器是否支持变动事件:(IE8及以前不支持)

1
var isSupported = document.implementation.hasFeature("MutationEvents", "2.0");


7、HTM/5事件

1、contextmenu 事件 contextmenu 事件链接

单击鼠标右键可以调出上下文菜单。


2、beforeunload 事件 beforeunload 事件链接

在页面卸载前触发


3、DOMContentLoaded 事件

DOMContentLoaded事件则在形成完整的DOM树之后就会触发,不理会图像、JavaScript 文件、CSS文件或其他资源是否已经下载完毕。与load 事件不同,window的load事件会在页面中的一切都加载完毕时触发。加载的外部资源过多而颇费周折。

要处理DOMContentLoaded事件,可以为document或window添加相应的事件处理程序(尽管这个事件会冒泡到window,但它的目标实际上是document )。 例子:

1
2
3
EventUtil.addHandler(document, "DOMContentLoaded", function (event) {
alert("Content loaded");
});

4、 readystatechange 事件

E为DOM文档中的某些部分提供了readystatechange事件。这个事件的目的是提供与文档或元素的加载状态有关的信息,但这个事件的行为有时候也很难预料。支持readystatechange事件的每个对象都有一个readyStage属性,可能包含下列5个值中的一个。

  • uninitialized(为初始化):对象正在加载数据;
  • loading(正在加载):对象正在加载数据;
  • loaded(加载完毕):对象加载数据我能撑;
  • interactive(交互):可以操作对象了,但还没有完全加载;
  • complete(完成):对象已经加载完毕。

这些状态看起来很直观,但并非所有对象都会经理readyState的这几个阶段。换句话说,如果某个阶段不适合某个对象,则该对象完全可能跳过该阶段;并没有规定哪个阶段适用于哪个对象。显然,这意味着readystatechange事件经常会少于4次,而readyState属性的值也不总是连续的。

对于document而言,值为“interactive”的readyStage也会在与DOMContentLoaded大致相同的时刻触发readystatechange事件。此时,DOM树已经加载完毕,可以安全地操作它了,因此就会进入交互(interactive)阶段。但与此同时,图像及其它外部文件不一定可用。下面来看一段处理readystatechange事件的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var EventUtil = {
addHandler: function (element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
}
};
EventUtil.addHandler(document, "readystatechange", function () {
if (document.readyState == "interactive") {
alert("Content loaded");
}
});

这个事件的event对象不会提供任何信息,也没有目标对象。

在与load事件一起使用时,无法预测两个事件触发的先后顺序。在包含较多或较大的外部资源的页面中,会在load事件触发之前先进入交互阶段;而在包含较少或较小的外部页面资源中,则很难说readystatechange事件会发生在load事件前面。

让问题变得复杂的是,交互阶段可能会遭遇也可能会晚于完成阶段出现,无法确保顺序。在包含较多外部资源的页面中,交互阶段更有可能遭遇完成阶段出现;而在页面中包含较少外部资源的页面中,交互阶段更有可能早于完成阶段出现;而在页面中包含较少外部资源的情况下,完成阶段先于交互阶段出现的可能性更大。因此,为了尽可能抢到先机,有必要同时检测交互很完成阶段,如下面的例子所示:

1
2
3
4
EventUtil.addHandler(document, "readystatechange", function (event) {
if (document.readyState == "interactive" || document.readyState == "complete");
alert("Contet loaded");
})

对于上面的代码来说,当readystatechange事件触发时,会检测document。这样编写代码可以达到与使用DOMContentLoaded十分相似的效果。

虽然使用readystatechange可以十分近地模拟DOMContentLoaded事件,但它们本质上还是不同的。在不同的页面中,load事件与readystatechange事件并不能保证以相同的顺序触发。Opera还支持这个事件的一个精简版,但由于其行为与IE中事件的不一致性,我们建议不要在Opera中使用该事件。

另外,script和link元素也会触发readystatechange事件,可以用来确定外部的JavaScript和CSS文件是否已经加载完成。与其它浏览器中一样,除非把动态创建的元素添加到页面中,否则浏览器不会开始下载资源。基于元素触发的readystatechange事件也存在同样的问题,即readyState属性无论等于“loaded”还是“complete”都可以表示资源已经可用。有时候,readyState会停在“loaded”阶段而永远不会“完成”;有时候,又会跳过“loaded”阶段而直接“完成”。于是还需要像对待document一样采用相同的编码方式。例如,下面展示了一段加载外部JavaScript文件的代码:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
var EventUtil = {
addHandler: function (element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
removeHandler: function (element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
getEvent: function (event) {
return event ? event : window.event;
},
getTarget: function (event) {
return event.target || event.srcElement;
}
};
EventUtil.addHandler(window, "load", function () {
//创建一个新的<script>元素
var script = document.createElement("script");
EventUtil.addHandler(script, "readystatechange", function (event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
if (target.readyState == "loaded" || target.readyState == "complete") {
EventUtil.removeHandler(target, "readystatechange", arguments.callee);
alert("Script Loaded");
}
});
script.src = "example.js";
document.body.appendChild(script);
});

个例子为新创建的<script>节点指定了一个事件处理程序。事件的目标是该节点本身,因此当触发reaystatechange事件时,要检测目标的readyState属性是不是等于“loaded”或“complete”。如果进入了其中任何一个阶段,则移除事件处理程序(以防止被执行了两次),并显示一个警告框。与此同时,就可以执行已经加载完毕的外部文件的函数了。

同样的编码方式也使用于通过元素加载CSS文件的情况,如下面的例子所示

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var EventUtil = {
addHandler: function (element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
removeHandler: function (element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
getEvent: function (event) {
return event ? event : window.event;
},
getTarget: function (event) {
return event.target || event.srcElement;
}
};
EventUtil.addHandler(window, "load", function () {
//创建一个新的<link>元素
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";

EventUtil.addHandler(link, "readystatechange", function (event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
if (target.readyState == "loaded" || target.readyState == "complete") {
EventUtil.removeHandler(target, "readystatechange", arguments.callee);
alert("CSS Loaded");
}
});

link.href = "example.css";
document.getElementsByTagName("head")[0].appendChild(link);
});

同样,最重要的是要一并检测readyState的两个状态,并在调用了一次事件处理程序后就将其移除。

Opera也支持<script>元素上的readysatechange事件,但不支持<link>元素上的readystatechange事件。


5、onpageshow 事件 onpageshow 事件链接

onpageshow 事件在用户浏览网页时触发。


6、onpagehide 事件onpagehide 事件链接

onpagehide 事件在用户离开网页时触发。


7、onhashchange 事件 onhashchange 事件链接

onhashchange 事件在当前 URL 的锚部分(以 ‘#’ 号为开始) 发生改变时触发 。

必须要把hashchange事件处理程序添加给window对象,然后URL参数列表只要变化就会调用它。此时的event对象应该额外包含两个属性:oldURL和newURL。这两个属性分别保存着参数列表变化前后的完整URL。

8、设备事件

Mobile 事件参考手册



5、内存和性能

1、事件委派(事件委托)

对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一一个事
件处理程序,就可以管理某一类型的所有事件。 例如,click 事件会- -直冒泡到document层次。也就
是说,我们可以为整个页面指定一个onclick事件处理程序,而不必给每个可单击的元素分别添加事
件处理程序。以下面的HTML代码为例。

1
2
3
4
5
<ul id="myLinks">
<li id="goSomewhere">Go somewhere</li>
<li id="doSomething">Do something</li>
<li id="sayHi">Say hi</li>
</ul>

添加点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13

var item1 = document.getElementById("goSomewhere");
var item2 = document.getElementById("doSomething");
var item3 = document.getElementById("sayHi");
EventUtil.addHandler(item1, "click", function (event) {
location.href = "http://www.wrox.com";
});
EventUtil.addHandler(item2, "click", function (event) {
document.title = "I changed the document's title";
});
EventUtil.addHandler(item3, "click", function (event) {
alert("hi");
});

如果在一一个复杂的Web应用程序中,对所有可单击的元素都采用这种方式,那么结果就会有数不清的代码用于添加事件处理程序。此时,可以利用事件委托技术解决这个问题。使用事件委托,只需在DOM树中尽量最高的层次上添加一一个事件处理程序,如下面的例子所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var list = document.getElementById("myLinks");
EventUtil.addHandler(list, "click", function(event) {
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
switch (target.id) {
case "doSomething":
document.title = "I changed the document's title";
break;
case "goSomewhere":
location.href = "http://www.wrox.com";
break;
case "sayHi":
alert("hi");
break;
}
});

事件委托的优点:

  • document对象很快就可以访问,而且可以在页面生命周期的任何时点上为它添加事件处理程序(无需等待DOMContentLoaded或load事件)。 换句话说,只要可单击的元素呈现在页面上,就可以立即具备适当的功能。
  • 在页面中设置事件处理程序所需的时间更少。只添加一一个事件处理程序所需的DOM引用更少,所花的时间也更少。
  • 整个页面占用的内存空间更少,能够提升整体性能。

最适合进行事件委托的事件有: click 、mousedown 、 mouseup 、 keydown 、keyup 、keypress 。



2、移除事件处理程序

每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的JavaScript代码之间就会建立一一个连接。这种连接越多,页面执行起来就越慢。如前所述,可以采用事件委托技术,限制建立的连接数量。另外,在不需要的时候移除事件处理程序,也是解决这个问题的一种方案。内存中留有那些过时不用的“空事件处理程序”( dangling event handler ),也是造成Web应用程序内存与性能问题的主要原因。

在两种情况下,可能会造成上述问题。第一种情况就是从文档中移除带有事件处理程序的元素时。这可能是通过纯粹的DOM操作,例如使用removeChild ()和replaceChild()方法, 但更多地是发生在使用innerHTML替换页面中某一 部分的时候。如果带有事件处理程序的元素被innerHTML删除了,那么原来添加到元素中的事件处理程序极有可能无法被当作垃圾回收。来看下面的例子。

1
2
3
4
5
6
7
8
9
10
<div id="myDiv">
<input type="button" value="Click Me" id="myBtn">
</div>
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function() {
//?执行某些操作
document.getElementById("myDiv").innerHTML = "Processing..."; //??了!
};
</script>

这里,有一个按钮被包含在<div>元素中。为避免双击,单击这个按钮时就将按钮移除并替换成一条消息;这是网站设计中非常流行的一种做法。但问题在于,当按钮被从页面中移除时,它还带着一个事件处理程序呢。在<div> 元素上设置innerHTML可以把按钮移走,但事件处理程序仍然与按钮保持着引用关系。有的浏览器(尤其是IE)在这种情况下不会作出恰当地处理,它们很有可能会将对元素和对事件处理程序的引用都保存在内存中。如果你知道某个元素即将被移除,那么最好手工移除事件处理程序,如下面的例子所示。

1
2
3
4
5
6
7
8
9
10
11
<div id="myDiv">
<input type="button" value="Click Me" id="myBtn">
</div>
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function() {
//?执行某些操作
btn.onclick = null; // ????????
document.getElementById("myDiv").innerHTML = "Processing...";
};
</script>

一般来说,最好的做法是在页面卸载之前,先通过onunload事件处理程序移除所有事件处理程序。
在此,事件委托技术再次表现出它的优势一需要跟踪的事件处理程序越少,移除它们就越容易。对这种类似撤销的操作,我们可以把它想象成:只要是通过onload事件处理程序添加的东西,最后都要通过onunload事件处理程序将它们移除。


总结性能优化:
  • 限制一个页面中事件处理程序的数量,数量太多会导致占用大量内存,而且也会让用户感觉页面反应不够灵敏。
  • 建立在事件冒泡机制之上的事件委托技术,可以有效地减少事件处理程序的数量。
  • 建议在浏览器卸载页面之前移除页面中的所有事件处理程序。



愿你的坚持终有收获。