JavaScript 与 HTML 之间的交互是经过事件实现的。事件,就是文档或者浏览器窗口中发生的一些特定的交互瞬间。可使用侦听器(或处理程序)来预约事件,以便事件发生时执行相应的代码。相似于设计模式中的观察员模式。支持页面的行为与页面的外观之间的松散耦合。javascript
事件流描述的是从页面中接收事件的顺序。html
IE和 Netscape 开发团队竟然提出了差很少是彻底相反的事件流的概念。IE 的事件流是事件冒泡流,而Netscape Communicator 的事件流是事件捕获流。java
1.事件冒泡node
IE的事件流叫作事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,而后逐级向上传播到较为不具体的节点(文档)。windows
<!DOCTYPE html> <html> <head> <title>event js</title> </head> <body> <div id="myDiv">Click Me</div> </body> </html>
上例中,若是你点击了页面中的div 元素, 那么这个click 事件会按照以下顺序传播:设计模式
也就是说,click事件首先在<div> 元素上发生,而这个元素就是咱们单机的元素。而后,click事件沿着DOM树向上传播,在每一级节点上都会发生,知道传播到 document 对象。浏览器
全部现代浏览器都支持事件冒泡,但在具体实现上还有一些差异。IE 5.5 及更早的版本中的事件冒泡会跳过<html> 元素。IE九、firefox、 Chrome 和 Safari 则将事件一直冒泡到 window 对象。缓存
2.事件捕获app
Netscape Communicator 团队提出的另外一种事件流叫作事件捕获(event capturing)。事件波活的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件事件捕获的用意在于它事件到达预约目标以前捕获它。仍然以上面的html 页面做为演示事件捕获的例子。那么单击<div>元素就会如下列顺序除法click 事件。框架
在事件捕获过程当中,document 对象首先接收到click 事件,而后事件沿着 DOM 树依次向下,一直传播到事件的实际目标,即<div> 元素。
虽然事件捕获是 Netscape Communicator 惟一支持的事件流模型,但IE九、Safari、Chrome、Opera 和 Firefox 目前也支持这种事件流模型。进管DOM2 及事件规范要求事件应该从document 对象开始传播,但这些浏览器都是从 window 对象开始捕获事件的。
因为老版本的浏览器不支持,所以不多有人使用事件捕获。因此建议使用事件冒泡,在有特殊须要是在使用事件捕获。
3.DOM事件流
DOM2级事件 规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。而后是实际的目标接收到事件。最后一个阶段是冒泡阶段,能够在这个阶段对事件作出相应。
事件就是用户或浏览器自身执行的某种动做。诸如 click、load、mouseover,都是事件的名字,而相应某个事件的函数就叫作事件处理程序(或事件侦听器)。事件处理程序的名字以on 开头,所以 click 事件的事件处理程序就是 onclick,以此类推。
1.某个元素支持的每种事件,均可以使用一个与相应事件处理程序同名的HTML 特性来制定。这个特性的值应该是可以执行的 JavaScript 代码。
<div id="myDiv" onclick="alert('Clicked')">Click Me</div>
也能够调用其余地方定义的脚本:
<div id="myDiv" onclick="showMessage()">Click Me</div> <script type="text/javascript"> function showMessage(){ alert("Hello world!"); } </script>
在HTML 中指定事件处理程序存在一个时差问题:由于用户可能会在HTML 元素一出如今页面上就触发相应的事件,但当时的事件可能尚不具有执行条件。如上例中,假设代码尚未加载进来用户就点击了div,就会引起错误。为此,不少HTML 事件处理程序都会被封装在一个try-catch 块中,以便错误不会浮出水面。
<div id="myDiv" onclick="try{showMessage();}catch(ex){}">Click Me</div>
还有一个缺点是HTML 与 JavaScript 代码紧密耦合。若是要更换事件处理程序,就要改动两个地方:HTML 代码 和 JavaScript 代码。这正是许多开发人员摒弃HTML 事件处理程序,转而使用JavaScript 制定事件处理程序的缘由。
2.DOM0 级事件处理程序
每一个元素(包括window 和 document)都有本身的事件处理程序属性,这些属性一般所有小写,例如:onclick。将这种属性设置为一个函数,就能够指定事件处理程序。
var div = document.getElementById("myDiv");
div.onclick = function(){
alert("click");
}
使用DOM0 级方法制定的事件处理程序被认为是元素的方法。所以这时候的事件处理程序是在元素的做用域中运行,因此承诺须中的this 引用当前元素。
var div = document.getElementById("myDiv"); div.onclick = function(){ alert(this.id); //myDiv }
能够经过将事件处理程序属性的值设置为null 来删除经过DOM0 级方法制定的事件处理程序。
div.onclick = null;
3.DOM2 级事件处理程序
DOM2级事件 定义了两个方法,用于处理制定和删除事件处理程序的操做:addEventListener() 和 removeEventListener()。全部DOM节点都包含者两个方法,而且他们都接受三个参数:要处理的事件名、做为事件处理程序的函数 和 一个布尔值。最后这个布尔值参数若是是true,表示在捕获节点调用事件处理程序;若是是false,表示在冒泡阶段调用事件处理程序。
var div = document.getElementById("myDiv"); div.addEventListener("click", function(){ alert(this.id); }, false);
使用DOM2 级 方法添加事件处理程序的主要好处是能够添加多个事件处理程序。
var div = document.getElementById("myDiv"); div.addEventListener("click", function(){ alert(this.id); }, false); div.addEventListener("click", function(){ alert("hello world"); }, false);
经过addEventListener() 方法添加的事件处理程序只能使用 removeEventListener() 来移除,移除时传入的参数与添加处理程序使用的参数相同。这也意味着 经过 addEventListener() 添加的匿名函数将没法移除。
var div = document.getElementById("myDiv"); var handler = function(){ alert(this.id); } div.addEventListener("click", handler, false); div.removeEventListener("click", handler, false);
大多数状况下,都是将事件处理程序添加到事件流的冒泡阶段,这样能够最大限度地兼容各类浏览器。最好只在须要在事件到达目标以前截获它的时候才将事件处理程序添加到捕获阶段。若是不是特别须要,不建议在事件捕获阶段注册事件处理程序。
4.IE 事件处理程序
IE实现了与DOM中相似的两个方法:attachEvent() 和 detachEvent()。这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数。因为IE8 及更造版本只支持事件冒泡,因此经过attachEvent()添加的事件处理程序都会被添加到冒泡阶段。
div.attachEvent("onclick", function(){ alert("clicked"); });
在IE中使用attachEvent() 与使用DOM0 级方法的主要区别在于事件处理程序的做用域。在使用DOM0级方法的状况下,事件处理程序会在其所属元素做用域中运行;在使用attachEvent() 方法的状况下,事件处理程序会在全局做用域中运行,所以this 等于 window。
一样,经过attachEvent() 方法添加的匿名函数没法经过detachEvent() 来移除,detachEvent() 同attachEvent() 方法的参数是同样的。
5.跨浏览器的事件处理程序
只要恰当的使用能力检测,就能够编写本身的跨浏览器的方式处理事件。只须要关注冒泡阶段。
第一个要建立的方法是addHandler(),它的职责是是状况分别使用DOM0 级方法、DOM2级方法或者IE方法来添加事件。这个方法属于一个名为EventUtil 的对象。addHandler() 方法接收三个参数:要操做的元素、事件名称 和 事件处理程序函数。
与 addHandler() 对应的方法是 removeHandler() ,它也接受相同的参数。这个方法的职责是移除以前添加的事件处理程序。
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; } } };
在触发DOM上的某个事件时,会产生一个事件对象 event,这个对象中包含着全部与事件有关的信息。包括致使事件的元素、事件类型 以及 其余与特定事件相关的信息。例如鼠标操做致使的事件对象中,会包含鼠标位置信息,而键盘操做致使的事件对象中,会包含与按下的键有关的信息。全部的浏览器都支持event对象,但支持的方式不一样。
1.DOM中的事件对象
兼容DOM的浏览器会将一个 event 对象传入到事件处理程序中。不管指定事件处理程序时使用什么方法(DOM0级或DOM2级),都会传入event对象。
event 对象包含与建立它的特定事件有关的属性和方法。触发的事件类型不同,可用的属性和方法也不同。不过全部事件都会有下列成员。
属性/方法 | 类型 | 读/写 | 说明 |
currentTarget | Element | 只读 | 其事件处理程序当前正在处理事件的那个元素。 |
target | Element | 只读 | 事件的目标。 |
type | String | 只读 | 被触发的事件类型。 |
bubbles | Boolean | 只读 | 代表事件是否冒泡。 |
stopImmediatePropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡,同时组织任何事件处理程序被调用(DOM3 级事件中新增) |
stopPropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡。若是bubbles 为 true,则可使用这个方法。 |
eventPhase | Integer | 只读 | 调用事件处理程序的阶段:1表示捕获阶段,2表示“处于目标”,3表示冒泡阶段。 |
cancelable | Boolean | 只读 | 代表是否能够取消事件的默认行为。 |
preventDefault() | Function | 只读 | 取消事件的默认行为。若是cancelable 是 true,则可使用这个方法。 |
defaultPrevented | Boolean | 只读 | 为true 表示已经调用了 preventDefault() (DOM3 级事件中新增)。 |
detail | Integer | 只读 | 与事件相关的细节信息。 |
trusted | Element | 只读 | 为true 表示见见是浏览器生成的。为false 表示事件是由开发人员经过调用JavaScript建立的(DOM3级事件中新增)。 |
view | AbstractView | 只读 | 与事件关联的抽象视图。等同于发生事件的 window 对象。 |
在事件处理程序内部,对象this 始终等于 currentTarget 的值,而 target 则只包含事件的实际目标。若是直接将事件处理程序指定给了目标元素,则this、currentTarget 和 target 包含相同的值。
var div = document.getElementById("myDiv"); div.onclick = function(event){ alert(event.currentTarget === this); //true alert(event.target === this); //true };
若是事件处理程序存在于节点的父节点中,那么currentTarget、this 将表明父节点,而target 表示的是子节点。
var div = document.getElementById("myDiv"); document.body.onclick = function(event){ alert(event.currentTarget === this); //true alert(this === document.body); //true alert(event.target === div); //true };
要组织特定事件的默认行为,可使用 preventDefault() 方法。例如,连接的默认行为就是在被单击时会导航到其 href 特性制定的URL。若是你想组织连接导航这一默认行为,那么经过连接的onclick 事件处理程序能够取消它。
var link = document.getElementById("myLink"); link.onclick = function(event){ event.preventDefault(); };
使用 stopPropagation() 方法能够当即中止事件在DOM层次中的传播,即取消进一步的事件捕获或者冒泡。例如,直接添加到一个节点的事件处理程序能够调用 stopPropagation(),从而避免除法注册在document.body 上面的事件处理程序。
var div = document.getElementById("myDiv"); document.body.onclick = function(event){ alert("hello"); //不会执行 }; div.onclick = function(event){ event.stopPropagation(); alert("propagation stoped"); };
只有在事件处理程序执行期间,event对象才会存在,一旦事件处理程序执行完成,event对象就会被销毁。
2.IE中的事件对象
与访问DOM中的event 对象不一样,要访问IE中的event对象有集中不一样的方式,取决于指定事件处理程序的方法。在使用DOM0 级方法添加事件处理程序时, event 对象做为window 对象的一个属性存在。
var div = document.getElementById("myDiv"); btn.onclick = function(){ var event = window.event; alert(event.type); //click };
若是事件处理程序是使用attachEvent() 添加的,那么会有一个event 对象做为参数被传入事件处理程序函数中。
var div = document.getElementById("myDiv"); div.attachEvent("onclick", function(event){ alert(event.type); });
在像这样使用attachEvent() 的状况下,也能够经过window 对象来访问event 对象,就像使用DOM0 级方法时同样。不过方便起见,同一个对象也会做为参数传递。
IE 的 event 对象一样也包含与建立它的事件相关的属性和方法。
属性/方法 | 类型 | 读/写 | 说明 |
cancelBubble | Boolean | 读/写 | 默认值为false,但将其设置为true就能够取消事件冒泡(与DOM中的 stopPropagation() 方法的做用相同)。 |
returnValue | Boolean | 读/写 | 默认值为true,但将其设置为false 就能够取消事件的默认行为(与DOM中的preventDefault() 方法的做用相同)。 |
srcElement | Element | 只读 | 事件的目标(与DOM 中的target 属性相同)。 |
type | String | 只读 | 被触发的事件的类型。 |
由于事件处理程序的做用域是根据制定它的方式来肯定的,因此不能认为this 会始终你等于事件目标,故而,最好仍是使用 event.srcElement 比较保险。
var div = document.getElementById("myDiv"); div.onclick = function(){ alert(window.event.srcElement === this); // true }; div.attachEvent("onclick", function(event){ alert(event.srcElement === this); //false });
3.跨浏览器的事件对象
继续补充EventUtil对象,增长对event 对象的支持。
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; } } };
使用时须要在页面中加入引入js文件的代码,使用方法以下:
<!DOCTYPE html> <html> <head> <title>event js</title> </head> <body> <div id="myDiv">Click Me</div> <script type="text/javascript" src="./eventutil.js"></script> <script type="text/javascript"> var div = document.getElementById("myDiv"); div.onclick = function(event){ event = EventUtil.getEvent(event); alert(event.type); //click alert(EventUtil.getTarget(event).id); //myDiv }; </script> </body> </html>
Web浏览器中可能发生的事件有不少类型。不一样的事件类型具备不一样的信息,而DOM3级事件规定了一下几类事件。
除了这几类事件以外,HTML5 也定义了一组事件,而有些浏览器还会在DOM 和BOM 中实现其余专有事件。这些转悠事件通常都是根据开发人员需求定制的,没有什么规范,所以不一样浏览器实现有可能不一致。
1.UI事件
UI事件指的是那些不必定与用户操做有关的事件。这些事件在DOM规范出现以前,都是以这种或那种形式存在的,而在DOM 规范中保留是为了向后兼容。现有的UI 事件以下:
以上这些事件在DOM2级事件中都被归为HTML 事件,要肯定浏览器是否支持DOM2级事件规定的HTML事件,可使用如下代码:
var isSupported = document.implementation.hasFeature("HTMLEvents", "2.0");
要肯定浏览器是否支持DOM3级事件 定义的事件,可使用以下代码:
var isSupported = document.implementation.hasFeature("UIEvent", "3.0");
1.1 load事件
当页面彻底加载后(包括全部图像、JavaScript文件、CSS文件等外部资源),就会触发window 上面的load事件。有两种定义onload 事件处理程序的方式。第一种使用JavaScript 代码:
EventUtil.addHandler(window, "load", function(event){ alert("Loaded!"); });
这里的event对象不包含有管这个事件的任何附加信息,但在兼容DOM的浏览器中,event.target 属性的值会被设置为document,而IE并不会为这个事件设置srcElement 属性。
第二种制定onload事件处理程序的方式是为body元素添加一个onload 特性,
<!DOCTYPE html> <html> <head> <title>event js</title> </head> <body onload="alert('Loaded!')"> <div id="myDiv">Click Me</div> </body> </html>
通常来讲在window 上面发生的任何事件均可以在<body> 元素中经过相应的特性来指定,由于在HTML中没法访问window 元素。可是建议尽量的使用JavaScript 方式。
在建立新的<img>元素时,能够为其制定一个事件处理程序,以便图像加载完毕后给出提示。此时最重要的是要在指定src属性以前先指定事件:
EventUtil.addHandler(window, "load", function(event){ var image = document.createElement("img"); EventUtil.addHandler(image, "load", function(event){ event = EventUtil.getEvent(event); alert(EventUtil.getTarget(event).src); //file:///home/zzl/Study/nodejs/studyjs/snal.jpg }); document.body.appendChild(image); image.src = "snal.jpg"; });
新图像元素不必定要从添加到文档后才开始下载,只要设置了sr属性就会开始下载。
一样的功能也能够经过使用DOM0 级的Image对象实现。在DOM出现以前,开发人员常用Image 对象在客户端预先加载图像。能够像使用<img>元素同样使用Image对象,只不过没法将其添加到DOM树中。
EventUtil.addHandler(window, "load", function(event){ var image = new Image(); //document.createElement("img"); EventUtil.addHandler(image, "load", function(event){ event = EventUtil.getEvent(event); alert(EventUtil.getTarget(event).src); //file:///home/zzl/Study/nodejs/studyjs/snal.jpg }); document.body.appendChild(image); image.src = "snal.jpg"; });
有的浏览器将Image对象实现为<img>元素,但并不是全部浏览器都如此,因此最好将他们区别对待。
1.2 unload 事件
这个事件在文档被彻底卸载后触发。只要用户从一个页面切换到另外一个页面,就会触发unload 事件。而利用这个事件最多的状况是清除引用,以免内存泄露。
同load事件相似,也有两种制定onunload 事件处理程序的方式。第一种是使用JavaScript:
EventUtil.addHandler(window, "unload", function(event){ alert("Unloaded!"); });
具体来讲,当发生如下状况时,会发出 unload 事件:
可是实际上,这行代码并无执行,查了不少资料发现,由于onunload函数是在body已经关闭后才调动的,因此若是使用alert的话(alert父窗口是body)就会报错或不显示!
第二种方法仍然是在<body>元素添加一个特性(与load事件相似)。
<!DOCTYPE html> <html> <head> <title>event js</title> </head> <body onunload="alert('Unloaded!')"> <div id="myDiv">Click Me</div> </body> </html>
1.3. resize事件
当浏览器窗口被调整到一个新的高度或宽度时,就会触发resize 事件。这个事件是在window 窗口上触发。
关于什么时候会触发resize 事件,不一样浏览器有不一样的机制,IE、Safari、Chrome和Opera 会在浏览器窗口变化了1像素时就触发resize事件,而后随着变化不断重复触发。Firefox则最实惠在用户中止调整窗口大小是才会触发resize事件。因此应该注意不要在这个事件中添加大计算量的代码,由于这些代码有可能被频繁执行,从而致使浏览器反应明显变慢。
2.焦点事件
焦点事件会在页面元素得到或时区焦点是触发。利用这个事件并与 document.hasFocus() 方法及 document.activeElement 属性配合,能够直销用户在页面上的行踪。
3.鼠标与滚轮事件
鼠标事件是Web开发中最经常使用的一类事件,毕竟鼠标仍是最主要的定位设备。DOM3 级事件中定义了9个鼠标事件:
只有在同一个元素上相机触发mousedown 和 mouseup 事件,才会触发 click 事件;若是mousedown 或 mouseup 中的一个被取消,就不会触发click 事件。相似的,只有触发两次click 事件,才会触发一次 dblclick 事件,若是有代码阻止了连续两次触发click,那么就不会触发dblclick 事件了。这四个事件触发的顺序始终以下:
<!DOCTYPE html> <html> <head> <title>event js</title> </head> <body> <div id="myDiv">Click Me</div> <div id="consol"></div> <script type="text/javascript" src="./eventutil.js"></script> <script type="text/javascript"> var click = document.getElementById("myDiv"); var info = document.getElementById("consol"); EventUtil.addHandler(click, "click", function(event){ var p = document.createElement("p"); p.appendChild(document.createTextNode("clicked")); info.appendChild(p); }); EventUtil.addHandler(click, "mousedown", function(event){ var p = document.createElement("p"); p.appendChild(document.createTextNode("mousedowned")); info.appendChild(p); }); EventUtil.addHandler(click, "mouseup", function(event){ var p = document.createElement("p"); p.appendChild(document.createTextNode("mouseuped")); info.appendChild(p); }); EventUtil.addHandler(click, "dblclick", function(event){ var p = document.createElement("p"); p.appendChild(document.createTextNode("dblclicked")); info.appendChild(p); }); </script> </body> </html>
双击后输出样式以下:
鼠标事件中还有一类滚轮事件,其实就是一个mousewheel 事件。稍后介绍。
3.1. 客户区坐标位置
鼠标事件都是在浏览器视口中特定位置上发生的。这个位置信息保存在事件对象的clientX 和clientY 属性中。全部浏览器都支持这两个属性,他们的值表示事件发生时,书白哦指针在视口中的水平和垂直坐标。
EventUtil.addHandler(click, "click", function(event){ var p = document.createElement("p"); event = EventUtil.getEvent(event); p.appendChild(document.createTextNode("Client coordinates: " + event.clientX + ", " + event.clientY)); info.appendChild(p); });
3.2. 页面坐标位置
页面坐标位置表示的坐标是从页面自己而非视口左边和顶边计算的,用pageX 和 pageY 表示。
EventUtil.addHandler(click, "click", function(event){ var p1 = document.createElement("p"); event = EventUtil.getEvent(event); p1.appendChild(document.createTextNode("Client coordinates: " + event.clientX + ", " + event.clientY)); var p2 = document.createElement("p"); p2.appendChild(document.createTextNode("Page coordinates: " + event.pageX + ", " + event.pageY)); info.appendChild(p1); info.appendChild(p2); });
在页面没有滚动的状况下,pageX 和pageY 的值与 clientX 和 clientY 的值相等。
3.3. 屏幕坐标位置
这个位置是相对于整个电脑屏幕的位置。经过screenX 和 screenY 属性表示。
3.4.修改键
虽然鼠标事件主要是使用鼠标来触发的,但在按下鼠标时键盘上的某些键的状态也能够影响到所要采起的操做。这些修改键就是 Shift、Ctrl、Alt、Meta(在windows键盘中是windows键,在苹果机中是 Cmd键),他们常常被用来修改鼠标事件的行为。DOM为此规定了四个属性,表示这些修改键的状态:shiftKey、ctrlKey、altKey 和 metaKey。这些属性包含的都是布尔值,true表示相应的键被按下,反之则为false。
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(", ")); });
3.5. 相关元素
在发生mouseover 和 mouseout 事件时,还会涉及更多的元素。这两个事件都会涉及把鼠标指针从一个元素的边界以内移动到另外一个元素的边界以内。
对mouseover事件而言,事件的主要目标是得到光标的元素,而相关元素就是哪一个失去光标的元素。相似的,对mouseout事件而言,事件的主目标是失去光标的元素,而相关的元素则是得到光标的元素。
DOM经过event 对象的 relatedTarget 属性提供了相关元素的信息。这个属性只对于mouseover 和 mouseout 事件才包含值;对于其余事件,这个属性的值是null。 IE8 及以前的版本不支持relatedTarget属性,但提供了保存着一样信息的不一样属性。在mouseover事件中 fromElement 属性中保存了相关元素;在mouseout 事件中 toElement 属性中保存着相关元素。
添加到EventUtil 对象中,以实现跨浏览器取得相关元素:
var EventUtil = { //。。。。 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; } } };
使用方法以下:
var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "mouseover", function(event){ event = EventUtil.getEvent(event); var target = EventUtil.getTarget(event); var relatedTarget = EventUtil.getRelatedTarget(event); alert("Mouse move from " + relatedTarget.tagName + " to " + target.tagName); //Mouse move from HTML to DIV });
3.6. 鼠标按钮
只有在主鼠标按钮被单击(或键盘回车键被按下)才会触发click 事件,所以检测按钮的信息并没必要要。但对于mousedown 和 mouseup 事件来讲,则在其event 对象存在一个button 属性,表示按下或释放的按钮。DOM的button 属性可能有以下3个值:
IE8 及以前版本也提供了button 属性,但这个属性的值与DOM 的button 属性截然不同。
因为单独使用能力检测没法肯定差别(两种模型有同名的button 属性),所以能够经过检测浏览器是否支持DOM版鼠标事件来判断是否IE浏览器。
var EventUtil = { //。。。 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; } } } };
3.7 触摸设备
iOS 和 Android 设备的实现很是特别,由于这些设备没有鼠标。在面向iPhone和 iPad 中的Safari 开发是,要记住:
4.键盘与文本事件
有3个键盘事件:
虽然全部的元素都支持以上3个事件,可是只有在用户经过文本框输入文本时才最经常使用到。
只有一个文本事件:textInput。这个事件是对keypress 的补充,用意是将文本显示给用户以前更容易拦截文本。在文本插入文本框以前会触发textInput 事件。
在用户按了一下键盘上的字符键时,首先会触发keydown 事件,而后紧跟着是 keypress 事件,最后会触发keyup 事件。其中keydown和keypress 都是在文本框发生变化以前被触发的;而keyup事件则是在文本框已经发生变化以后被触发的。
若是用户按下的是一个非字符键,那么首先会触发keydown 事件,而后就是keyup 事件。
键盘事件与鼠标事件同样,都支持相同的修改键。并且键盘事件的事件对象中也有 shiftKey、ctrlKey、altKey 和 metaKey 属性。
4.1 键码
在发生keydown 和 keyup 事件时, event对象的 keyCode 属性中会包含一个代码,与键盘上一个特定的键对应。对数字字母字符键,keyCode 属性的值与 ASCII 码中对应小写字母或数字的编码相同。所以数字键7的keyCode 值是 55, 而字母 A键的keyCode 值为 65 ----与Shift键的状态无关。
4.2 字符编码
发生keypress 事件意味着按下的键会影响到屏幕中文本的显示。在全部的浏览器中,按下可以插入或删除的字符的键都会触发keypress 事件;按下其余键可否触发keypress 事件因浏览器而异。
event 对象还支持一个 charCode 属性,这个属性只有在发生keypress 事件时才包含值,并且这个值是按下的那个键所表明字符的ASCII码。此时的keyCode一般等于0或者也可能等于所按键的键码。IE8 及以前的版本和Opera 则是在keyCode 中保存字符的ASCII编码。想要以跨浏览器的方式取得字符编码,必须首先检测 charCode属性是否可用,若是不可用则使用keyCode。
var EventUtil = { //。。。。 getCharCode: function(event){ if(typeof event.charCode == "number"){ return event.charCode; }else{ return event.keyCode; } } };
获取案件的ASCII码以后,经过String.fromCharCode() 方法就能够转换成实际的字符。
var textbox = document.getElementById("myText"); EventUtil.addHandler(textbox, "keypress", function(event){ event = EventUtil.getEvent(event); alert(String.fromCharCode(EventUtil.getCharCode(event))); });
4.3 DOM3级变化
尽管全部浏览器都实现了某种形式的键盘事件,DOM3级事件仍是作出了一些改变。好比DOM3级事件中的键盘事件,不在包含charCode属性,而是包含两个新属性:key 和 char。
其中,key 属性是为了取代 keyCode 而新增的,它的值是一个字符串。在按下某个字符键时,key 的值就是相应的文本字符;在按下非字符键时,key 的值是相应键的名(如“Shift”或“Down”)。而char属性在按下字符键时的行为与key相同,但在按下非字符键时值为null。
IE9支持key 属性,但不支持char 属性。Safari5 和 Chrome 支持名为 keyIdentifier的属性,在按下非字符键(如shift)的状况下与key的值相同。对于字符键,keyIdentifier 返回一个格式相似“U+0000”的字符串,表示Unicode 值。
var textbox = document.getElementById("myText"); EventUtil.addHandler(textbox, "keypress", function(event){ event = EventUtil.getEvent(event); var identifier = event.key || event.keyIdentifier; if(identifier){ alert(identifier); } });
因为存在跨浏览器的问题,所以不推荐使用key、keyIdentifier 或者 char。
DOM3级事件还添加了一个名为location的属性,这是一个数值,表示按下了什么位置上的键:0表示默认键盘,1表示左侧位置(例如左侧的Alt键),2表示右侧位置(例如右侧的Shift键),3表示数字小键盘,4表示移动设备键盘(也就是虚拟键盘),5表示手柄(如任天堂wii控制器)。IE9支持这个属性。Safari 和Chrome 支持名为keyLocation的等价属性,但有Bug----值始终是0,除非按下了数字键盘返回3,否子不会是1\2\4\5
同key属性同样,支持location 的浏览器也很少,因此在跨浏览器开发中不推荐使用。
4.4. textInput 事件
DOM3级事件规范中引入了一个新事件,名叫textInput。根据规范,当用户在可编辑区域中输入字符时,会触发这个事件。这个用于替代keypress的textInput 事件的行为稍有不一样,区别之一就是任何能够得到焦点的元素均可以触发keypress 事件,但只有可编辑区才能触发textInput 事件。区别之二是 textInput 事件只会在用户按下可以输入实际字符的键时才被触发,而keypress 事件则在按下那些可以影响文本显示的键是也会触发(例如退格键)。
因为textInput 事件主要考虑的是字符,所以它的event对象中还包括一个data属性,这个属性的值就是用户输入的字符(而非字符编码)。例如:
var textbox = document.getElementById("myText"); EventUtil.addHandler(textbox, "textInput", function(event){ event = EventUtil.getEvent(event); alert(event.data); });
5.变更事件
DOM2级的变更事件能在DOM中的某一部分发生变化是给出提示。变更事件是给XML或 HTML DOM 设计的,兵不特定于某种语言。DOM2 级定义了以下变更事件:
使用下面的代码能够检测出浏览器是否支持变更事件:
var isSupported = document.implementation.hasFeature("MutationEvents", "2.0");
IE8 及更早版本不支持任何变更事件。
6.HTML5事件
HTML5 详尽列出了浏览器应该支持的全部事件。其中部分已经获得完善支持。
6.1. contextmenu 事件
为了实现上下文菜单,开发人员面临的一个主要问题是如何肯定应该显示上下文菜单(在windows中是右键单击,在Mac中是Ctrl + 单击),以及如何屏蔽与该操做关联的默认上下文菜单。为了解决这个问题,,就出现了contextmenu 这个事件,用于表示合适应该显示上下文菜单,以便开发人员取消默认的上下文菜单而提供自定义的菜单。
因为contextmenu 事件是冒泡的,所以能够为document 指定一个事件处理程序,用于处理页面中发生的全部此类事件。这个事件的目标是发生用户操做的元素。在全部浏览器中均可以取消这个事件:在兼容DOM 的浏览器中,使用 event.preventDefault(); 在IE 中,将event.returnValue 设为false。
由于contextmenu 事件 属于鼠标事件,因此其事件对象中包含与光标位置有关的全部属性。一般使用contextmenu 事件来显示自定义的上下文菜单,是使用onclick 事件处理程序来隐藏该菜单。co
<!DOCTYPE html> <html> <head> <title>Right Click </title> </head> <body> <div id="myDiv">right click</div> <ul id="myMenu" style="position:absolute;visibility:hidden;background-color:silver"> <li><a href="http://www.baidu.com">baidu</a></li> <li><a href="http://www.yahoo.com.cn">yahoo</a></li> <li><a href="http://www.163.com">163</a></li> </ul> <script type="text/javascript" src="eventutil.js"></script> <script type="text/javascript"> EventUtil.addHandler(window, "load", function(event){ var div = document.getElementById("myDiv"); EventUtil.addHandler(div, "contextmenu", function(event){ event = EventUtil.getEvent(event); EventUtil.preventDefault(event); var menu = document.getElementById("myMenu"); menu.style.left = event.clientX +"px"; menu.style.top = event.clientY + "px"; menu.style.visibility = "visible"; }); EventUtil.addHandler(document, "click", function(event){ document.getElementById("myMenu").style.visibility = "hidden"; }); }); </script> </body> </html>
支持contextmenu 事件的浏览器有IE、Firefox、Safari、Chrome 和 Opera 11+.
6.2beforeunload 事件
之因此有发生在window对象上的beforeunload 事件,是为了让开发人员有可能在页面卸载前阻止这一操做。这个事件会在浏览器卸载页面以前触发,能够经过它来取消卸载并继续使用原有页面。可是不恩可以完全取消这个事件,由于那就至关于让用户没法离开当前页面了。
对于IE 及 Firefox 而言,经过将event.returnValue 设置为要显示给用户的字符串能够返回确认关闭对话框;对于Safari 和 Chrome 而言,须要将处理函数返回该字符串
EventUtil.addHandler(window, "beforeunload", function(event){ event = EventUtil.getEvent(event); var message = "I'm really going to miss you if you go."; event.returnValue = message; //for IE & Firefox return message; //for Safari & Chrome });
Chrome 弹出对话框以下:
6.3. DOMContentLoaded 事件
window 的load 事件会在页面中的一切都加载完毕时触发,但这个过程可能会由于要加载的外部资源过多而颇费周折。而DOMContextLoaded 事件在造成完整的 DOM树以后就会触发,不理会图像、JavaScript 文件、CSS 文件或其余资源是否已经下载完毕。与load 事件不一样,DOMContentLoaded 支持在页面下载的早期添加事件处理程序,这也意味着用户能够尽早地与页面进行交互。
处理DOMContentLoaded 事件,能够为document 或 window 添加相应是事件处理程序,其event不会提供额外的信息,target属性是document。 这个事件始终都会在load 事件以前触发。
EventUtil.addHandler(document, "DOMContentLoaded", function(event){ alert("Content loaded"); });
IE9+、FireFox、Chrome、Safari 3.1+ 和 Opera 9+ 都支持DOMContentLoaded 事件。
6.4. readystatechange 事件
readystatechange 事件的目的是提供与文档或元素加载状态有关的信息,支持此事件的每一个对象都有一个 readyState 属性,可能包含下列5个值中的一个:
6.5. pageshow 和 pagehide 事件
Firefox 和 Opera 有一个特性,名叫“往返缓存”(back-forward cache 或 bfcache),能够在用户使用浏览器的“后退”和“前进”按钮是加快页面的转换速度。这个缓存中不只保存者页面数据,还保存了DOM 和 JavaScript 的状态;其实是将整个页面都保存在了内存里。若是页面位于bfcache里,那么再次打开该页面就不会触发load事件。为了更形象地说明bfcache 的行为, Firefox 仍是提供了一些新事件。
第一个事件就是pageshow,这个事件在页面显示时触发,不管该页面是否来自bfcache。在从新加载的页面中,pageshow 会在load 事件触发后触发;而对于bfcache 中的页面,pageshow 会在页面状态彻底回复的那一刻触发。虽然这个事件的目标是document, 但必须将其事件处理程序添加到window。
第二个事件是pagehide 事件,该事件会在浏览器卸载页面的时候触发。
支持pageshow 和 pagehide 事件的浏览器有 FIrefox、Safari 5+、Chrome 和 Opera。
6.6. hashchange 事件
HTML5 增长了 hashchange事件,以便在URL的参数列表(及URL中“#”后面的全部字符串)发生变化时通知开发人员。之因此新增这个事件,是由于在Ajax应用中,开发人员常常要利用URL参数列表来保存状态或导航信息。
因为事件处理程序能够为现代Web应用提供交互能力,所以不少开发人员会部分青红皂白地向页面中添加大量的处理程序。在JavaScript 中,添加到页面上的事件处理程序数量将直接关系到页面的总体运行性能。致使这一问题的缘由是多方面的。首先每一个函数都是对象,都会占用内存;内存中对象越多,性能就越差。其次,必须事先指定全部事件处理程序而致使的DOM 访问次数,会延迟整个页面的交互就绪事件。事实上,从如何利用好事件处理程序的角度出发,仍是有一些方法可以提高性能的。
1.事件委托
对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就能够管理某一类型的全部事件。例如,click 事件会一直冒泡到document 层次。也就是说,咱们能够为整个页面制定一个onclick 事件处理程序,而没必要给每一个可单击的元素分别添加事件处理程序。
<!DOCTYPE html> <html> <head> <title>title</title> </head> <body> <ul> <li id="goBaidu">baidu</li> <li id="changeTitle">title</li> <li id="sayHi">sayHi</li> </ul> <script type="text/javascript" src="eventutil.js"></script> <script type="text/javascript"> var goBaidu = document.getElementById("goBaidu"); var changeTitle = document.getElementById("changeTitle"); var sayHi = document.getElementById("sayHi"); EventUtil.addHandler(goBaidu, "click", function(event){ location.href = "http://www.baidu.com"; }); EventUtil.addHandler(changeTitle, "click", function(event){ document.title = "I changed the title"; }); EventUtil.addHandler(sayHi, "click", function(event){ alert("hi"); }); </script> </body> </html>
若是在一个负载的Web 应用程序中,对全部可点击的元素都采用这种方式,呢么结果就会有数不清的代码用户添加事件处理程序。此时,能够利用事件委托技术解决这个问题。使用事件委托,只须要在DOM树中尽可能最高的层次上添加一个事件处理程序。
var list = document.getElementById("myLink"); EventUtil.addHandler(list, "click", function(event){ event = EventUtil.getEvent(event); var target = EventUtil.getTarget(event); switch(target.id){ case "goBaidu": location.href = "http://www.baidu.com"; break; case "changeTitle": document.title = "I changed the title"; break; case "sayHi": alert("hi"); break; } });
若是可行的话,也能够考虑为document对象添加一个事件处理程序,用以处理页面上发生的某种特定类型的事件。这样作与采起传统的作法相比具备如下优势:
最适合采用事件委托技术的事件包括 click、mousedown、mouseup、keydown、keyup 和 keypress。虽然mouseover 和mouseout 事件也冒泡,但要适当处理他们并不容易,并且常常须要计算元素的位置。
2.移除事件处理程序
每当事件处理程序制定给元素时,运行中的浏览器代码与支持页面交互的JavaScript 代码 之间就会创建一个链接。这种链接越多,页面执行起来就越慢。如前所述能够采用事件委托技术,限制创建的链接数量。另外在不须要的时候移除事件处理程序,也是解决这个问题的一种方案。内存中留有那些过期不用的“空事件处理程序”(dangling event handler),也是形成Web应用程序内存与性能问题的主要缘由。
通常来讲,最好的作法是在页面卸载以前,先经过onunload 事件处理程序移除全部事件处理程序。在此,事件委托技术再次表现出它的优点,须要跟踪的事件处理程序越少,移除他们就越容易。对这种相似撤销的操做,咱们能够把它想象成:只要经过onload 事件处理程序 添加的东西,最后都要经过onunload 事件处理程序将它移除。
事件,就是网页中某个特别值得关注的瞬间。事件常常有用户操做或经过其余浏览器功能来触发。其实,也可使用JavaScript 在任意时刻触发特定的事件,而此时的事件就如同浏览器建立的事件同样。也就是说这些事件该冒泡还会冒泡,并且照样可以致使浏览器执行已经制定的处理他们的事件处理程序。
1.DOM中的事件模拟
能够造document对象上使用 createEvent() 方法建立event 对象。这个方法接收一个参数,即表示要建立的事件类型的字符串。在DOM2级中,全部的这些字符串都是使用英文复数形式,而在DOM3级中都变成了单数。这个字符串能够是下列几个字符串之一
在建立了event 对象以后,还须要使用与事件有关的信息对其进行初始化,每种类型的event对象都有一个特殊的方法,为他传入适当的数据u,就能够初始化该event 对象。不一样类型的这个方法的名字也不相同,具体要取决与 createEvent() 中使用的参数。
模拟事件的最后一部就是触发事件。这一步须要使用dispatchEvent() 方法,全部支持事件的DOM节点都支持这个方法。调用dispatchEvent() 方法时,须要传入一个参数,即表示要触发事件的event对象。触发事件后,改时间就跻身“官方事件”之列了,于是可以照样冒泡并引起相应事件处理程序的执行。
1.1.模拟鼠标事件
建立新的鼠标事件对象并为其指定必要的信息,就能够模拟鼠标事件。建立鼠标事件对象的方法是为createEvent() 传入字符串MouseEvents。返回的对象有一个名为 initMouseEvent() 方法,用于制定与该鼠标事件有关的信息,这个方法接收15个参数,分别与鼠标事件中每一个典型的属性一一对应:
其中前四个参数对正确触发事件相当重要,由于浏览器要用到这些参数。
var sayHi = document.getElementById("sayHi"); var event = document.createEvent("MouseEvents"); event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); sayHi.dispatchEvent(event);
事件是将JavaScript 与网页联系在一块儿的主要方式。DOM3级事件规范和HTML5 定义了常见的大多数事件。即便有规范定义了基本事件,但不少浏览器仍然在规范以外实现了本身的转悠事件,从而为开发人员提供更多掌握用户交互的手段。
在使用事件时,须要考虑以下一些内存与性能方面的问题。
事件是JavaScript 中最重要的主题之一,深刻理解事件的工做机制以及他们对性能的影响相当重要。