本章内容:javascript
事件流描述的是从页面中接受事件的顺序。但有意思的是,IE和Netscape开发团队竟然提出了差很少彻底相反的事件流的概念。IE的事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕获流。html
即事件开始时由最具体的元素接收,而后逐级向上传播到较为不具体的节点。java
全部现代浏览器都支持事件冒泡,但在具体实现上仍是有一些差异。IE5.5及更早版本中的事件冒泡会跳过<html>
元素(从<body>
直接跳到document
)。IE九、Firefox、Chrome和Safari则将事件一致冒泡到window对象。android
事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。ios
DOM2级事件规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。而后是实际的目标接收到事件。最后一个阶段是冒泡阶段,能够在这个阶段对事件作出响应。
多数支持DOM事件流的浏览器都实现了一种特定的行为;即便“DOM2级事件”规范明确要求捕获阶段不会涉及事件目标,但IE九、Safari、Firefox和Opera9.5及更高版本都会在捕获阶段触发事件对象上的事件。结果,就是有两个机会在目标对象上面操做事件。git
事件就是用户或浏览器自身执行的某种动做。诸如click、load和mouseover,都是事件的名字。而响应某个事件的函数就是事件处理程序。事件处理程序的名字以“on”开头,所以click事件的事件处理程序就是onclick,load事件的事件处理程序就是onload。为事件指定处理程序的方式有好几种。github
<script type='text/javascript'>
function showMessage() {
alert('hello world');
}
</script>
<input type='button' value='Click Me' onclick='showMessage()' />
复制代码
在这个例子中,单击按钮就会调用showMessage()函数。这个函数是在一个独立的<script>
元素中定义的,固然也能够被包含在一个外部文件中。事件处理程序中的代码在执行时,有权访问全局做用域中的任何代码。web
这样指定事件处理程序具备一些独到之处。首先,这样会建立一个封装着元素属性值的函数。这个函数中有一个局部变量event,也就是事件对象:api
<input type='button' value="Click Me" onClick='alert(event.type)'>
复制代码
经过event变量,能够直接访问事件对象,你不用本身定义它,也不用从函数的参数列表中读取。在这个函数内部,this值等于事件的目标元素。数组
<input type='button' value='Click Me' onclick='alert(this.value)'>
复制代码
关于这个动态建立的函数,另外一个有意思的地方是它扩展做用域的方式。在这个函数内部,能够像访问局部变量同样访问document及该元素自己的成员。这个函数使用with像下面这样扩展做用域:
function () {
with(document) {
with(this) {
// 元素属性值
}
}
}
复制代码
如此一来,事件处理程序要访问本身的属性就简单多了。下面这行代码与前面的例子效果相同:
<input type='button' value='Click Me' onclick='alert(value)'>
复制代码
若是当前元素是一个表单输入元素,则做用域中还会包含访问表单元素的入口,这个函数就变成了以下所示:
<form method='post'>
<input type='text' name='username' value=''>
<input type='button' value='Echo Username' onclick='alert(username.value)'>
</form>
复制代码
在这个例子中,单击按钮会显示文本框中的文本。值得注意的是,这里直接引用了username元素。
不过,在HTML中指定事件处理程序有两个缺点。
<input type='button' value='Click Me' onclick='showMessage()' />
<script type='text/javascript'>
function showMessage() {
alert('hello world');
}
</script>
复制代码
若是用户在页面解析showMessage()函数以前就单击了按钮,就会引起错误。为此,不少HTML事件处理程序都会被封装在一个try-catch块中,以便错误不会浮出水面。
<input type='button' value='Click Me' onclick='try{showMessage()}catch(ex){}'>
复制代码
另外一个缺点就是,这样扩展事件处理程序的做用域链在不一样浏览器中会致使不一样结果。不一样Javascript引擎遵循的标识符解析规则略有差别,极可能会在访问非限定对象成员时出错。
最后一个缺点是HTML与Javascript代码紧密耦合。若是要更换事件处理程序,就要改动两个地方:HTML代码和Javascript代码。
经过JavaScript指定事件处理程序的传统方式,就是讲一个函数赋值给一个事件处理程序属性。这种为事件处理程序赋值的方法是在第四代Web浏览器中出现的,并且至今仍然为全部现代浏览器所支持。缘由:
每一个元素都有本身的事件处理程序属性,这些属性一般所有小写,例如onclick。将这种属性的值设置为一个函数,就能够指定事件处理程序:
var btn = document.getElementById('myBtn');
btn.onclick = function () {
alert('Clicked');
}
复制代码
有一些缺点:
在这些代码运行之前不会指定事件处理程序,所以若是这些代码在页面中位于按钮后面,就有可能在一段时间内怎么单击都没反应。
使用DOM0级方法指定的事件处理程序被认为是元素的方法。所以,这时候的事件处理程序是在元素的做用域中运行;换句话说,程序中的this引用当前元素。
<input type='button' id='myBtn'>
var btn = document.getElementById('myBtn');
btn.onclick = function () {
alert(this.id);
}
复制代码
单击按钮显示的是元素的ID,这个ID是经过this.id取得的。不只仅是ID,实际上能够在事件处理程序中经过this访问元素的任何属性和方法。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。
也能够删除经过DOM0级方法指定的事件处理程序,只要像下面这样将事件处理程序属性的值设置为null便可:
btn.onclick = null;
复制代码
“DOM2级事件”定义了两个方法,用于处理指定和删除事件处理程序的操做:addEventListener()和removeEventListener()。全部DOM节点中都包含这两个方法,而且它们都接收3个参数:要处理的事件名、做为事件处理程序的函数和一个布尔值。最后这个布尔值参数若是是true,表示在捕获阶段调用事件处理程序;若是是false,表示在冒泡阶段调用事件处理程序。
要在按钮上为click事件添加事件处理程序,可使用下列代码:
var btn = document.getElementById('myBtn');
btn.addEventListener('click', function () {
alert(this.id);
}, false);
复制代码
上面的代码为一个按钮添加了onclick事件处理程序,并且该事件会在冒泡阶段被触发。与DOM0级方法同样,这里添加的事件处理程序也是在其依附的元素的做用域中运行。使用DOM2级方法添加事件处理程序的好处是能够添加多个事件处理程序。来看下面的例子。
var btn = document.getElementById('MyBtn');
btn.addEventListener('click', function () {
alert('Hello world');
}, false);
复制代码
经过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除;移除时传入的参数与添加处理程序时使用的参数相同。这也意味着经过addEventListener()添加的匿名函数将没法移除:
var btn = document.getElementById('myBtn');
btn.addEventListener('click', function () {
alert(this.id);
}, false)
// 这里省略了其余代码
btn.removeEventListener('click', function () {
alert(this.id);
}, false) // 这样无效
var handler = function () {
alert(this.id);
}
btn.addEventListener('click', handler, false)
btn.removeEventListener('click', handler, false) // 这样有效
复制代码
大多数状况下,都是将事件处理程序添加到事件流的冒泡阶段,这样能够最大限度地兼容各类浏览器。最好只在须要在事件到达目标以前截获它的时候将事件处理程序添加到捕获阶段。若是不是特别须要,咱们不建议在事件捕获阶段注册事件处理程序。
IE实现了与DOM中相似的两个方法:attachEvent()和detachEvent()。这两个方法接收相同的两个参数:事件处理程序与事件处理程序函数。因为IE8及更早版本只支持事件冒泡,因此经过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。
要使用attachEvent()为按钮添加一个事件处理程序,可使用如下代码。
var btn = docunment.getElementById('myBtn');
btn.attachEvent('onclick', function () {
alert('Clicked');
})
复制代码
在IE中使用attachEvent()与使用DOM0级方法的主要区别在于事件处理程序的做用域。在使用DOM0级方法的状况下,事件处理程序会在全局做用域运行,所以this等于window。
来看下面的例子。
var btn = document.getElementById('myBtn');
btn.attachEvent('onclick', function () {
alert(this=== window); // true
})
复制代码
在编写跨浏览器的代码时,牢记这一区别很是重要。
使用attachEvent()添加的事件能够经过detachEvent()来移除,条件是必须提供相同的参数。与DOM方法同样,这也意味着添加的匿名函数将不能被移除。不过,只要可以将对相同函数的引用传给dtachEvent(),就能够移除相应的事件处理程序。
为了以跨浏览器的方式处理事件,很多开发人员会使用可以隔离浏览器差别的JavaScript库。
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.detach('on'+ type, handler);
} else {
element['on' + type] = null;
}
}
}
复制代码
在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含着全部与事件有关的信息。包括致使事件的元素、事件的类型以及其余与特定事件相关的信息。
兼容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.type属性表示的事件类型。这个属性始终都会包含被触发的事件类型。
event对象包含与建立它的特定事件有关的属性和方法。触发的事件类型不同,可用的属性和方法也不同。不过,全部事件都会有下表列出的成员。
属性/方法 | 类型 | 读/写 | 说明 |
---|---|---|---|
preventDefault() | Function | 只读 | 取消事件的默认行为。若是cancelable是true,则可使用这个方法 |
stopImmediatePropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡。同时阻止任何事件处理程序被调用(DOM3级事件中新增) |
stopPropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡。若是bubbles为true,则可使用这个方法 |
target | Element | 只读 | 事件的目标 |
trusted | Boolean | 只读 | 为true表示事件是浏览器生成的。为false表示事件是由开发人员经过Javascript建立的(DOM3级事件中新增) |
type | String | 只读 | 被触发的事件的类型 |
view | AbstractView | 只读 | 与事件关联的抽象视图。等同于发生事件的window对象 |
看下面例子:
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事件就冒泡到document.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 'mouseout':
event.target.style.backgroundColor = '';
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
复制代码
要阻止特定事件的默认行为,可使用preventDefault()方法。例如,连接的默认行为就是在被单击时会导航到其href特性指定的URL。若是你想阻止连接导航这一默认行为,那么经过连接onclick事件处理程序能够取消它。
只有cancelable属性设置为true的事件,才可使用preventDefault()来取消其默认行为。
另外,stopPropagation()方法用于当即中止事件在DOM层次中的传播,即取消进一步的事件捕获或冒泡。例如,直接添加到一个按钮的事件处理程序能够调用stopPropagation(),从而避免触发注册在document.body上面的事件处理程序,以下面的例子所示。
事件对象的eventPhase属性,能够用来肯定事件当前正位于事件流的哪一个阶段。若是是在捕获阶段调用的事件处理程序,eventPhase等于1;若是事件处理程序处于目标对象上,则eventPhase等于2;若是在冒泡阶段调用的事件处理程序,eventPhase等于3。
注意,尽管“处于目标”发生在冒泡阶段,但eventPhase仍然一直等于2。
当eventPhase等于2时,this、target和currentTarget始终是相等的。
只有在事件处理程序执行期间,event对象才会存在;一旦事件处理程序执行完成,event对象就会被销毁。
与访问DOM中的event对象不一样,要访问IE中的event对象有几种不一样的方式,取决于指定事件处理程序的方法。在使用DOM0级方法添加事件处理程序时,event对象做为window对象的一个属性存在。来看下面的例子。
var btn = document.getElementById('myBtn');
btn.onclick = function () {
var event = window.event;
alert(event.type) //'click'
}
复制代码
但是,若是事件处理程序是使用attachEvent()添加的,那么就会有一个event对象做为参数被传入事件处理程序函数中,以下所示。
var btn = document.getElementById('myBtn');
btn.attacehEvent('onclick', function (event) {
alert(event.type); //'click'
})
复制代码
在像这样使用attachEvent()状况下,也能够经过window对象来访问event对象,就像使用DOM0级方法时同样。不过为方便起见,同一个对象也会做为参数传递。
若是是经过HTML特性指定的事件处理程序,那么还能够经过一个名叫event的变量来访问event对象(与DOM中的事件模型相同)。
<input type='button' value='Click Me' onclick='alert(event.type)' >
复制代码
IE的event对象一样也包含与建立它的事件相关的属性和方法。其中不少属性和方法都有对应的或者相关的DOM属性和方法。与DOM的event对象同样,这些属性和方法也会由于事件类型的不一样而不一样,但全部事件对象都会包含下表所列的属性和方法。
属性/方法 | 类型 | 读/写 | 说明 |
---|---|---|---|
cancelBubble | Boolean | 读/写 | 默认值false,但将其设置为true就能够取消事件冒泡(与DOM中的stopPropagation()方法的做用相同) |
returnValue | Boolean | 读/写 | 默认值为true,将其设置为false就能够取消事件的默认行为(与DOM中的preventDefault()方法的做用相同) |
srcElement | Element | 只读 | 事件的目标(与DOM中的target属性相同) |
type | String | 只读 | 被触发的事件的类型 |
由于事件处理程序的做用域是根据指定它的方式来肯定的,因此不能认为this会始终等于事件目标。故而,最好仍是使用event.srcElement比较保险。
虽然DOM和IE中的event对象不一样,但基于它们之间的类似性依旧能够拿出跨浏览器的方案来。IE中event对象的所有信息和方法DOM对象中都有,只不过实现方式不同。不过,这种对应关系让实现两种事件模型之间的映射很是容易。能够对前面介绍的EventUtil对象加以加强,添加以下方法以求同存异。
var EventUtil = {
addHandler: function (element, type, handler) {},
removeHandler: function (element, 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;
}
},
stopPropagation: function (event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
}
复制代码
跨浏览器阻止事件冒泡:
var btn = document.getElementById('myBtn');
btn.onclick = function (event) {
alert('Clicked');
event = EventUtil.getEvent(event);
EventUtil.stopPropagation(event);
}
document.body.onclick = function (event) {
alert('Body clicked');
}
复制代码
Web浏览器中可能发生的事件有不少类型。
“DOM3级事件”规定了如下几类事件。
UI事件指的是那些不必定与用户操做有关的事件。这些事件在DOM规范出现以前,都是以这个或那种形式存在的,而在DOM规范中保留是为了向后兼容。
现有的UI事件以下:
<input>或<textarea>
)中的一或多个字符时触发。多数这些事件都与window对象或表单控件相关。
要肯定浏览器是否支持DOM2级事件规定的HTML事件,可使用以下代码:
var isSupported = document.implementation.hasFeature('HTMLEvents', '2.0');
复制代码
最经常使用的一个事件。当页面彻底加载后(包括全部图像、JavaScript文件、CSS文件等外部资源),就会触发window上面的load事件。
有两种定义onload事件处理程序的方式。
第一种方式是使用以下所示的JavaScript代码:
EventUtil.addHandler(window, 'load', function (event) {
alert('Loaded!');
})
复制代码
第二种指定onload事件处理程序的方式是为<body>
元素添加一个onload特性:
<!DOCTYPE html>
<html>
<head>
<title>Load Event Example</title>
</head>
<body onload='alert("loaded!")'>
</body>
</html>
复制代码
通常来讲,在window上面发生的任何事件均可以在<body/>
元素中经过相应的特性来指定,由于在HTML中没法访问window元素。实际上,这只是为了保证向后兼容的一种权宜之计。建议尽量使用javascript方式。
图像上面也能够触发load事件,不管是在DOM中的图像元素仍是HTML中的图像元素。所以,能够在HTML中为任何图像指定onload事件处理程序:
<img src='smile.gif' onload='alert("Image loaded.")'>
复制代码
这样,当例子中的图像加载完毕后就会显示一个警告框。一样的功能可使用JavaScript实现:
var $image = document.getElementById('myImg');
$image.addEventListener('load', function (e) {
alert('image loaded');
})
复制代码
还有一些元素也以非标准的方式支持load事件。在IE9+、Firefox、Opera、Chrome和Safari 3+及更高版本中,<script>
元素也会触发load事件,以便开发人员肯定动态加载的JavaScript文件是否加载完毕。与图像不一样,只有设置了<script>
元素的src属性并将元素添加到文档后,才会开始下载JavaScript文件。换句话说,对于<script>
元素而言,指定src属性和指定事件处理程序的前后顺序就不重要了。
与load事件对应的是unload事件,这个事件在文档被彻底卸载后触发。只要用户从一个页面切换到另外一个页面,就会发生unload事件。而利用这个事件最懂的状况是清除引用,以免内存泄漏。
**不管使用那种方式,都要当心编写onunload事件处理程序中的代码。既然unload事件是在一切都被卸载以后才触发,那么在页面加载后存在的那些对象,此时就不必定存在了。此时,操做DOM节点或者元素的样式就会致使错误。
当浏览器窗口被调整到一个新的高度或宽度时,就会触发resize事件。
关于什么时候会触发resize事件,不一样浏览器有不一样的机制。IE、Safari、Chrome和Opera会在浏览器窗口变化了1像素就触发resize事件,而后随着变化不断重复触发。Firefox则只会在用户中止调整窗口大小时才会触发resize事件。
虽然scroll事件是在window对象上发生的,但它实际表示的则是页面中相应元素的变化。
**在混杂模式下,能够经过<body>
元素的scrollLeft和scrollTop来监控到这一变化;而在标准模式下,除Safari以外的全部浏览器都会经过<html>
元素来反映这一变化(Safari仍然基于<body>
跟踪滚动位置)。 **
与resize事件相似,scroll事件也会在文档被滚动期间重复被触发,因此有必要尽可能保持事件处理程序的代码简单。
焦点事件会在页面得到或失去焦点时触发。利用这些事件并与document.hasFocus()方法及document.activeElement属性配合,能够知晓用户在页面上的行踪,有如下6个焦点事件。
这一类事件中最主要的两个是focus和blur,它们都是javscript早期就获得全部浏览器支持的事件。这些事件的最大问题是不冒泡。所以,IE的focusin和focusout与Opera的DOMFocusIn和DOMFocusOut才会重叠。IE的方式最后被DOM3级事件采纳为标准方式。
当焦点从页面中的一个元素移动到另外一个元素,会依次触发下列事件:
即便focus和blur不冒泡,也能够在捕获阶段侦听它们。
下面演示全局侦听blur和focus
鼠标事件是Web开发中最经常使用的一类事件。DOM3级事件中定义了9个鼠标事件。
页面中的全部元素都支持鼠标事件。除了mouseenter和mouseleave,全部鼠标事件都会冒泡,也能够被取消,而取消鼠标事件将会影响浏览器的默认行为。取消鼠标事件的默认行为还会影响其余事件,由于鼠标事件与其余事件是密不可分的关系。
**只有在同一个元素上相继触发mousedown和mouseup事件,才会触发click事件;若是mousedown或mouseup中的一个被取消,就不会触发click事件。相似地,只有触发两次click事件,才会触发一次dblclick事件。若是有代码阻止了连续两次触发click事件,那么就不会触发dblclick事件了。
这4个事件触发的顺序始终以下:
使用如下代码能够检测浏览器是否支持以上DOM2级事件(除dbclick、mouseenter和mouseleave以外):
var isSupported = document.implementation.hasFeature('MouseEvents', '2.0');
复制代码
要检测浏览器是否支持上面的全部事件,可使用如下代码:
var isSupported = document.implementation.hasFeature('MouseEvents', '3.0');
复制代码
鼠标事件中还有一类滚轮事件。而说是一类事件,其实就是一个mousewheel事件。这个事件跟踪鼠标滚轮,相似于Mac的触控板。
鼠标事件都是在浏览器视口中特定的位置上发生的。这个位置信息保存在事件对象的clientX和clientY属性中。全部浏览器都支持这两个属性。
经过客户区坐标可以知道鼠标是在视口中什么位置发生的,而页面坐标经过事件对象的pageX和pageY属性,能告诉你事件是在页面中的什么位置发生的。换句话说,这两个属性表示鼠标光标在页面中的位置,所以坐标是从页面自己而非视口的左边和顶边计算的。
鼠标事件发生时,不只会有相对于浏览器窗口的位置,还有一个相对于整个电脑屏幕的位置。而经过screenX和screenY属性就能够肯定鼠标事件发生时鼠标指针相对于整个屏幕的坐标信息。
虽然鼠标事件主要是使用鼠标来触发的,但在按下了鼠标时键盘上的某些键的状态也能够影响到所要采起的操做。这些修改键就是Shift、Ctrl、Alt和Meta,它们常常被用来修改鼠标事件的行为。DOM为此规定了4个属性,表示这些修改键的状态:shiftKey、ctrlKey、altKey和metaKey。
在发生mouseover和mouseout事件时,还会涉及更多的元素。这两个事件都涉及把鼠标指针从一个元素的边界以内移动到另外一个元素的边界以内。对mouseover事件而言,事件的主目标是得到光标的元素,而相关元素就是那个失去光标的元素。相似的,对mouseout事件而言,事件的主目标是失去光标的元素,而相关元素则是得到光标的元素。
DOM经过event对象的relatdTarget属性提供了相关元素的信息。这个属性只对于mouseover和mouseout事件才包含值;对于其余事件,这个属性的值是null。IE8及以前版本不支持relatedTarget属性,但提供了保存着一样信息的不一样属性。在mouseover事件触发时,IE的fromElement属性中保存了相关元素;在mouseout事件触发时,IE的toElement属性中保存着相关元素。
只有在主鼠标按钮被单击时才会触发click事件,所以检测按钮的信息并非必要的。但对于mousedown和mouseup事件来讲,则在其event对象存在一个button属性,表示按下或释放的按钮。DOM的button属性可能有以下3个值:0表示主鼠标按钮,1表示中间的鼠标按钮,2表示次鼠标按钮。在常规的设置中,主鼠标按钮就是鼠标左肩,而次鼠标按钮就是鼠标右键。
在使用onmouseup事件处理程序时,button的值表示释放的是哪一个按钮。此外,若是不是按下或释放了主鼠标按钮,Opera不会触发mouseup或mousedown事件。
“DOM2级事件”规范在event对象中还提供了detail属性,用于给出有关事件的更多信息。对于鼠标事件来讲,detail中包含了一个数值,表示在给定位置上发生了多少次单击。在同一个像素上相继地发生一次mousedown和一次mouseup事件算做一次单击。detail属性从1开始计数,每次单击发生后都会递增。若是鼠标在mousedown和mouseup之间移动了位置,则detail会被重置为0。
IE6.0首先实现了mousewheel事件。此后,Opera、Chrome和Safari也都实现了这个事件。当用户经过鼠标滚轮与页面交互、在垂直方向上滚动页面时,就会触发mousewheel事件。这个事件能够在任何元素上面触发,最终会冒泡到document或window对象。与mousewheel事件对应的event对象除包含鼠标事件的全部标准信息外,还包含一个特殊的wheelDelta属性。当用户向前滚动鼠标滚轮时,wheelDelta是120的倍数;当用户向后滚动鼠标滚轮时,wheelDelta是-120倍数。
IOS和android设备的实现很是特别,由于这些设备没有鼠标。在面向iPhone和iPod中的Safari开发时,要记住如下几点:
用户在使用键盘时会触发键盘事件。“DOM2级事件”最初规定了键盘事件,但在最终定稿以前又删除了相应的内容。结果,对键盘事件的支持主要遵循的是DOM0级。
“DOM3级事件”为键盘事件指定了规范,IE9率先彻底实现了该规范。其余浏览器也在着手实现这一标准,但仍然有不少问题。
只有一个文本事件:textInput。这个事件是对keypress的补充,用意是在将文本显示给用户以前更容易拦截文本。在文本插入文本框以前会触发textInput事件。
在用户按了一下键盘上的字符键时,首先会触发keydown事件,而后紧跟着是keypress事件,最后会触发keyup事件。其中,keydown和keypress都是在文本框发生变化以前被触发的;而keyup事件则是在文本框已经发生变化以后被触发的。若是用户按下了一个字符键不放,就会重复触发keydown和keypress事件,直到用户松开该键为止。
若是用户按下的是一个非字符键,那么首先会触发keydown事件,而后是keyup事件。若是按着不放,就会一直重复触发keydown事件,直到用户松开这个键,此时会触发keyup事件。
键盘事件与鼠标事件同样,都支持相同的修改键。并且,键盘事件的事件对象中也有shiftKey、ctrlKey、altKey和metaKey属性。IE不支持metaKey。
在发生keydown和keyup事件时,event对象的keyCode属性中会包含一个代码,与键盘上一个特定的键对应。对数字字母字符键,keyCode属性的值与ASCII码中对应下泄字母或数字的编码相同。
所以,数字键7的keyCode值为55,而字母A键的keyCode值为65 ———— 与Shift键的状态无关。
发生keypress事件意味着按下的键会影响到屏幕中文本的显示。在全部浏览器中,按下可以插入或删除字符的键都会触发keypress事件;按下其余键可否触发此事件因浏览器而异。
尽管全部浏览器都实现了某种形式的键盘事件,DOM3级事件仍是作出了一些改变。好比,DOM3级事件中的键盘事件,再也不包含charCode属性,而是包含两个新属性:key和char。
其中,key属性是为了取代keyCode而新增的,它的值是一个字符串。在按下某个字符键时,key的值就是相应的文本字符(如k或M);在按下非字符键时,key的值是相应键的名(如Shift或Down)。而char属性在按下字符键时的行为与key相同,但在按下非字符键时值为null。
DOM3级事件还添加了一个名为location的属性,这是一个数值,表示按下了什么位置上的键:
“DOM3级事件”规范中引入了一个新事件,名叫textInput。根据规范,当用户在可编辑区域中输入字符时,就会触发这个事件。这个用于替代keypress的textInput事件的行为稍有不一样。
因为textInput事件主要考虑的是字符,所以它的event对象中包含一个data属性,这个属性的值就是用户输入的字符(而非字符编码)。换句话说,用户在没有按上档键的状况下按下了S键,data的值就是‘s',而若是在按住上档键时按下该键,data的值就是'S'。
任天堂Wii会在用户按下Wii遥控器上的按键时触发键盘事件。
复合事件是DOM3级事件中新添加的一类事件,用于处理IME的输入序列。
IME(Input Method Editor,输入法编辑器)可让用户输入在物理键盘上找不到的字符。例如,使用拉丁文键盘的用户经过IME照样能输入日文字符。IME一般须要同时按住多个键,但最终只输入一个字符。复合事件就是针对检测和处理这种输入而设计的。
复合事件与文本事件有不少方面都很类似。在触发复合事件时,目标是接收文本的输入字段。但它比文本事件的事件对象多一个属性data,其中包含如下几个值中的一个:
利用监听compositionstart判断是否开启了输入法。从而实现体验较为良好兼容性较强的监控字数的控件。
DOM2级的变更(mutation)事件能在DOM中的某一部分发生变化时给出提示。变更事件是为XML或HTML DOM设计的,并不特定于某种语言。DOM2级定义了以下变更事件。
使用下列代码能够检测出浏览器是否支持变更事件:
var isSupported = document.implementation.hasFeature('MutationEvents', '2.0');
复制代码
在使用removeChild()或replaceChild()从DOM中删除节点时,首先会触发DOMNodeRemoved事件。这个事件的目标(event.target)是被删除的节点,而event.relatedNode属性中包含着对目标节点父节点的引用。在这个事件触发时,节点还没有从其父节点删除,所以其parentNode属性仍然指向父节点。
若是被移除的节点包含子节点,那么在其全部子节点以及这个被移除的节点上会相继触发DOMNodeRemovedFromDocument事件。但这个事件不会冒泡,因此只有直接指定给其中一个子节点的事件处理程序才会被调用。这个事件的目标是相应的子节点或者那个被移除的节点,除此以外event对象中不包含其余信息。
紧随其后触发的是DOMSubtreeModified事件。这个事件的目标是被移除节点的父节点;此时的event对象也不会提供与事件相关的其余信息。
DOM规范没有涵盖全部浏览器支持的全部事件。HTML5详尽列出了浏览器应该支持的全部事件。本节只讨论其中获得浏览器完善支持的事件,但并不是所有事件。
contextmenu这个事件,用以表示什么时候应该显示上下文菜单,以便开发人员取消默认的上下文菜单而提供自定义菜单。
因为contextmenu事件是冒泡的,所以能够为document指定一个事件处理程序,用以处理页面中发生的全部此类事件。这个事件的目标是发生用户操做的元素。在全部浏览器中均可以取消这个事件。
这个事件的目标是发生用户操做的元素。在全部浏览器中均可以取消这个事件:在兼容DOM的浏览器中,使用event.preventDefault();在IE中,将event.returnValue()的值,设置为false。由于contextmenu事件属于鼠标事件,因此其事件对象中包含与光标位置有关的全部属性。一般使用contextmenu事件来显示自定义的上下文菜单,而使用onclick事件处理程序来隐藏该菜单。
之因此发生在window对象上的beforeunload事件,是为了让开发人员有可能在页面卸载前阻止这一操做。这个事件会在浏览器卸载页面以前触发,能够经过它来取消卸载并继续使用原有页面。可是,不能完全取消这个事件,由于那就至关于让用户没法离开当前页面了。为此,这个事件的意图是将控制权交给用户。
window的load事件会在页面中的一切都加载完毕时触发,但这个过程可能会由于要加载的外部资源过多而破费周折。而DOMContentLoaded事件则在造成完整的DOM树以后就会触发,不理会图像、JavaScript文件、CSS文件或其余资源是否已经下载完毕。
DOMContentLoaded事件对象不会提供任何额外的信息(其target属性是document)。
**对于不支持DOMContentLoaded的浏览器,咱们建议在页面加载期间设置一个时间为0毫秒的超时调用:
setTimeout(function () {
// 在此添加事件处理程序
}, 0);
复制代码
这个代码的实际意思就是:"在当前JavaScript处理完成后当即运行这个函数。"在页面下载和构建期间,只有一个JavaScript处理过程,所以超时调用会在该过程结束时当即触发。为了确保这个方法有效,必须将其做为页面中的第一个超时调用;即使如此,也仍是没法保证在全部环境中该超时调用必定会早于load事件被触发。
IE为DOM文档中的某些部分提供了readystatechange事件。这个事件的目的是提供与文档或元素的加载状态有关的信息。但这个事件的行为有时候也很难预料。支持readystatechange事件的每一个对象都有一个readystate属性,可能有下列5个值中的一个:
这些状态看起来很直观,但并不是全部对象都会经历readyState的这几个阶段。换句话说,若是某个阶段不适用某个对象,则该对象彻底可能跳过该阶段;并无规定哪一个阶段适用于哪一个对象。显然,这意味着readystatechange事件常常会少于4次,而readyState属性的值也不老是连续。
对于document而言,值为“interactive”的readyState会在与DOMContentLoaded大体相同的时刻触发readystatechange事件。此时,DOM树已经加载完毕,能够安全地操做它了,所以就会进入交互interactive阶段。
这个事件的event对象不会提供任何信息,也没有目标对象。
在与load事件一块儿使用时,没法预测两个事件触发的前后顺序。在包含较多或较大的外部资源的页面中,会在load事件触发以前先进入交互阶段;而在包含较少或较小的外部资源的页面中,则很难说readystatechange事件会发生在load事件前面。
让问题变得更复杂的是,交互阶段可能会早于也可能会晚于完成阶段出现,没法确保顺序。在包含较多外部资源的页面中,交互阶段更有可能早于完成阶段出现;而在页面中包含较少外部资源的状况下,完成阶段先于交互阶段出现的可能性更大。所以,为了尽量抢到先机,有必要同时检测交互和完成阶段。
var handler = function (e) {
if (document.readyState == 'interactive' || document.readyState == 'complete') {
document.removeEventListener('readystatechange', handler);
alert('Content loaded');
}
document.addEventListener('readystatechange', handler);
复制代码
Firefox和Opera有一个特性,名叫“往返缓存”(back-forward cache),能够在用户使用浏览器的“后退”和“前进”按钮时加快页面的转换速度。这个缓存中不只保存着页面数据,还保存了DOM和JavaScript状态;其实是将整个页面都保存在了内存里。若是页面位于bfcache中,那么再次打开该页面时就不会触发load事件。尽管因为内存中保存了整个页面的状态,不触发load事件也不该该会致使什么问题,但为了更形象地说明bfcache行为,Firefox仍是提供了一些新事件。
第一个事件就是pageshow,这个事件在页面显示时触发,不管该页面是否来自bgcache。在从新加载的页面中,pageshow会在load事件触发后触发;而对于bfcache中的页面,pageshow会在页面状态彻底恢复的那一刻触发。
另外值得注意的是,虽然这个事件的目标是document,但必须将其事件处理程序添加到window。
(function () {
var pageshowCount = 0;
window.addEventListener('pageshow', function (e) {
pageshowCount++;
notifyMe('page show: ' + pageshowCount);
})
})()
复制代码
这个例子使用了私有做用域,以防止变量showCount进入全局做用域。当页面首次加载完成时,showCount的值为0。此后,每当触发pageshow事件,showCount的值就会递增并经过警告框显示出来。若是你在离开包含以上代码的页面以后,又单击“后退”按钮返回该页面,就会看到showCount每次递增的值。这是由于该变量的状态,乃至整个页面的状态,都被保存在了内存中,当你返回这个页面时,它们的状态获得了恢复。若是你单击了浏览器的“刷新”按钮,那么showCount的值就会被重置为0,由于页面已经彻底从新加载了。
除了一般的属性以外,pageshow事件的event对象还包含了一个名为presisted的布尔值属性。若是页面被保存在了bfcache中,则这个属性的值为true;不然,这个属性的值为false。
经过检测persisted属性,就能够根据页面在bfcache中的状态来肯定是否须要采起其余操做。
与pageshow事件对于的是pagehide事件,该事件会在浏览器卸载页面的时候触发,并且是在unload事件以前触发。与pageshow事件同样,pagehide在document上面触发,但其事件处理程序必需要添加到window对象。这个事件的event对象也包含persisted属性,不过其用途稍有不一样。
HTML5新增了hashchange事件,以便在URL的参数列表发生变化时通知开发人员。之因此新增这个事件,是由于在Ajax应用中,开发人员常常要利用URL参数列来保存状态或导航信息。
**使用如下代码能够检测浏览器是否支持hashchange事件:
var isSupported = ('onhashchange' in window);
复制代码
智能手机和平板电脑的普及,为用户与浏览器交互引入了一种新的方式,而一类新事件也应运而生。device event可让开发人员肯定用户在怎样使用设备。
只要用户改变了设备的查看模式,就会触发orientationchange事件。此时的event对象不包含任何有价值的信息,由于惟一相关的信息能够经过window。orientation访问到。
全部IOS设备都支持orientationchange事件和window。orientation属性。
firefox提供。这个事件与ios中的orientationchange事件不一样,该事件只能提供一个平面的方向变化。因为MozOrientation事件是在window对象上触发的,因此可使用如下代码来处理。
本质上,DeviceOrientation Event规范定义的deviceorientation事件与MozOrientation事件相似。它也是在加速计检测到设备方向变化时在window对象上触发,并且具备与MozOrientation事件相同的支持限制。
触发deviceorientation事件时,事件对象中包含着每一轴相对于设备静止状态下发生变化的信息。事件对象包含如下5个属性。
devicemotion事件。这个事件是要告诉开发人员设备何时移动,而不只仅是设备方向如何改变。
事件对象包含如下属性:
具体来讲有如下几个触摸事件:
上面这几个事件都会冒泡,也均可以取消。虽然这些触摸事件没有在DOM规范中定义,但它们倒是以兼容DOM的方式实现的。所以,每一个触摸事件的event对象都提供了在鼠标事件中常见的属性。
除了常见的DOM属性外,触摸事件还包含下列三个用于跟踪触摸的属性。
有三个手势事件,分别介绍以下。
只有两个手指都触摸到事件的接收容器时才会触发这些事件。在一个元素上设置事件处理程序。若是另外一个手指又放在了屏幕上,则会先触发gesturestart事件,随后触发基于该手指的touchstart事件。若是一个或两个手指在屏幕上滑动,将会触发gesturechange事件。但只要有一个手指移开,就会触发gestureend事件,紧接着又会触发基于该手指的touchend事件。
触摸事件和手势事件之间存在某种关系。当一个手指放在屏幕上时,会触发touchstart事件。若是另外一个手指又放在了屏幕上,则会先触发gesturestart事件,随后触发基于该手指的touchstart事件。若是一个或两个手指在屏幕上滑动,将会触发gesturechange事件。但只要有一个手指移开,就会触发gestureend事件,紧接着又会触发基于该手指的touchend事件。
与触摸事件同样,每一个手势事件的event对象都包含着标准的鼠标事件属性:bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、ctrlKey、metaKey。此外,还包含两个额外的属性:rotation和scale。
其中,rotation属性表示手指变化引发的旋转角度,负值表示逆时针旋转,正值表示顺时针旋转。而scale属性表示两个手指间距离的变化状况。
触摸事件也会返回rotation和scale属性,但这两个属性只会在两个手指与屏幕保持接触时才会发生变化。通常来讲,使用基于两个手指的手势事件,要比管理触摸事件中的全部交互要容易得多。
在javascript中,添加到页面上的事件处理程序数量将直接关系到页面的总体运行性能。致使这一问题的缘由是多方面。首先,每一个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定全部事件处理程序而致使的DOM访问次数,会延迟整个页面的交互就绪时间。事实上,从如何利用好事件处理程序的角度出发,仍是有一些方法可以提高性能的。
对“事件处理程序过多”问题的解决方案就是事件委托。
在document对象添加一个事件处理程序,用以处理页面上发生的某种特定类型的事件。这样作与采起传统的作法相比具备以下优势。
每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的JavaScript代码之间就会创建一个链接。这种链接越多,页面执行起来就越慢。如前所述,能够采用事件委托技术,限制创建的链接数量。另外,在不须要的时候移除事件处理程序,也是解决这个问题的一种方案。内存中留有那些过期不用的“空事件处理程序”(dangling event handler),也是形成web应用程序内存与性能问题的主要缘由。
在两种状况下。可能会形成上述问题。
事件常常由用户操做或经过其余浏览器功能来触发。但不多有人直到,也可使用JavaScript在任意时刻来触发特定的事件,而此时的事件就如同浏览器建立的事件同样。
参考customEvent api
事件是将Javscript与网页联系在一块儿的主要方式。“DOM3级事件”规范和HTML5定义了常见的大多数事件。即便有规范定义了基本事件,但不少浏览器仍然在规范以外实现了本身的专有事件,从而为开发人员提供更多掌握用户交互的手段。
在使用事件时,须要考虑以下一些内存与性能方面的问题。
事件是JavaScript中最重要的主题之一,深刻理解事件的工做机制以及它们对性能的影响相当重要。