JavaScript高级程序设计-13:事件
JavaScript与HTML之间的交互是通过事件实现的。
一、事件流
首先我们要明白事件流的概念。当我们点击一个按钮时,也点击了按钮的容器元素,甚至也点击了整个事件。事件流描述就是从页面中接收事件的顺序。在主流浏览器中有两种事件接收方式。一种是IE提出的事件冒泡流,另一种是Netscape提出的事件捕获流。顾名思义,事件冒泡流是从被点击的最小元素逐渐向上索引DOM树,而事件捕获的思想是不太具体的节点先捕捉到事件,然后事件沿DOM树逐渐向下,一直传播到事件的实际目标。
由于老版本浏览器不支持事件捕获模型,所以建议开发人员尽量使用事件冒泡,在有特殊需要的情况下再使用事件捕获。
二、事件处理程序
事件就是用户或浏览器自身执行的某种动作,如click,load,mouseover,都是事件的名字;而响应某个事件的函数就叫做事件处理程序(或事件侦听器)。事件处理程序的名字以on开头,onclick,onmouseover相信大家都相当熟悉了。
2.1 HTML事件处理程序
<input type="button" value="click" onclick="alert("click!")"> //click!
<input type="button" value="click2" onclick="alert(event.type)"> //click
<input type="button" value="click3" onclick="alert(this.value)"> //click3
在指定了事件处理程序之后,会创建一个封装着元素属性的函数。这个函数中有一个局部变量event,也就是事件对象。还有一个局部变量this,等于事件的目标元素。
由于HTML事件处理程序会使HTML和JavaScript代码紧密耦合,所以许多开发人员都摈弃了HTML事件处理程序,转而使用JavaScript指定事件处理程序。
2.2 DOM0级事件处理程序
简单来说,就是在JS中,将一个函数赋值给一个元素的事件处理程序属性。这个时候,使用DOM0级方法制定的事件处理程序被认为是元素的方法。因此这时候的事件处理程序是在元素的作用域中运行,即可以在事件处理程序中通过this访问元素的任何属性和方法。
var btn = document.getElementById("btn");
btn.onclick = function(){ //此时onclick事件处理程序是元素的方法
alert(this.value);
}
在DOM0级事件处理程序是通过把函数实例的引用指派到DOM元素的属性而声明的。
DOM第0级的缺点是,属性被用于存储作为事件处理程序的函数的引用,所以每个元素对于任何特定的事件类型,每次只能注册一个事件处理程序。
2.3 DOM2级事件处理程序
DOM第2级事件模型(也称为监听器)被设计来解决这些类型的问题。每个DOM元素都定义名为addEventListener()的方法,用于把事件处理程序(监听器)附加到元素上。这个方法的格式如下所示: addEventListener(enentType,listener,useCapture) ;
使用DOM2级处理程序,我们能在同一个元素上为同一个事件类型建立多个事件处理程序。
2.4 IE事件处理程序
IE实现了与DOM类似的两个方法:attachEvent()和detachEvent()。需要注意的是,传入attachEvent中的参数是"onclick", 而不是DOM方法中的"click"。在IE中使用attachEvent()与DOM0级方法的主要区别在于事件处理程序的作用域。该方法会在全局作用域中运行,因此this等于window,在编写跨浏览器的代码时,这一点非常重要。attachEvent()和addEventListerner()一样,都可以多次为元素添加事件处理程序。但不同的是,attachEvent()是从后往前执行的。
2.5 兼容IE和其他浏览器的事件处理程序
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; }, preventDefault: function (event) { if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false; } }, stopPropagation: function (event) { if (event.stopPropagation) { event.stopPropagation(); } else { event.cancelBubbles = true; } }, getRelatedTarget: function (event) { if (event.relatedTarger) { return event.relatedTarget; } else if (event.toElement) { return event.toElement; } else if (event.fromElement) { return event.fromElement; } else { return null; } } }
三、事件对象
事件对象(event)包含了所有与事件相关的信息。所有的浏览器都支持事件对象(event),只不过支持的方式不同。
3.1 DOM中的事件对象
兼容DOM的浏览器会将一个event事件传入到事件处理程序中。无论指定事件处理程序用的是什么方法(DOM0还是DOM2级)。
var btn = document.getElementById("myBtn");
/*DOM 0 级方法指定事件处理程序*/
btn.onclick = function(event){
console.log(event.type);
};
/*DOM 2 级方法指定事件处理程序*/
btn.addEventListener("click", function(event){
console.log(event.type);
}, false);
在事件处理程序内部,始终存在三个对象:this, target, currentTarget. 其中this和currentTarget包含相同的值——是事件处理程序被指定的目标元素;而target则包含的是事件的实际目标。当直接将事件处理程序指定给目标元素时,this, target 和 currentTarget 包含相同的值。
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
console.log(event.target == this); //true
console.log(event.currentTarget == this); //true
};
/*如果事件处理程序存在于按钮的父节点中*/
var body =document.body;
body.onclick = function(event){
console.log(event.target == this);//target对象包含的是btn
console.log(event.currentTarget == this); //都包含body
};
stopPropagation()方法可以用于立即停止事件在DOM层次中的传播,即取消进一步的事件捕获或冒泡,从而避免出发注册在document.body上的事件处理程序,如下例子所示,当点击btn的时候,body的事件处理程序不会被触发,因为事件冒泡被及时取消了。
var btn = document.getElementById("myBtn");
var body = document.body;
btn.onclick = function(event){
console.log("btn was clicked!");
event.stopPropagation();
};
body.onclick = function(event){
console.log("body was clicked");
};
3.2 IE中的事件对象
首先需要明细的是,IE中不存在event.target,而是event.srcElement; firefox只支持target, chrome两个都支持。介于使用this不一定能获取到当前被触发的元素,所以建议编程者们使用target和srcElement获取目标元素。
此外,若在IE中使用DOM 0级制定事件处理程序,则event对象作为window对象的一个属性而存在,需要通过window.event调用;而若是使用attachEvent绑定事件,则event对象会作为参数被传入事件处理程序中。
四、事件类型
Web浏览器中可能发生的事件类型有很多,不同的事件类型具有不同的信息,而“DOM3级事件”规定了以下几类事件。
UI事件,焦点事件,鼠标事件,滚轮事件,文本事件,键盘事件,合成事件,变动事件。每种事件类型都包含了多种事件,这里不做过多阐述,最好还是在实际应用中选择重要的记忆。
这里要着重提一下HTML5事件。
1.contextmenu事件——用于自定义右键弹出的菜单。
2.beforeunload事件——用于阻止页面卸载
3.pageshow事件——在页面显示时被触发。若页面重新加载,则先出发load,再出发pageshow;若页面来自缓存,则pageshow立即被触发。
五、内存和性能
添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。由于浏览器必须事先指定所有事件处理程序而大量访问DOM,会延迟整个页面的交互时间。
对“事件处理程序过多"问题的解决办法就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
<!--我需要为这三个列表项添加事件--> <ul id="myLinks"> <li id="goSomewhere">go somewhere</li> <li id="doSomething">do something</li> <li id="sayHi">say hi</li> </ul>
/*正常情况下我需要为这三个元素分别指派事件*/
/*注意:要将此脚本写在DOM的底部,否则脚本无法获取DOM元素会出现null*/
var item1=document.getElementById("goSomewhere"); var item2=document.getElementById("doSomething"); var item3=document.getElementById("sayHi"); item1.addEventListener("click",function(event){location.href="http://www.baidu.com";},false); item2.addEventListener("click",function(event){document.title="look!";},false); item3.addEventListener("click",function(event){alert("Hi!");},false);
var list = document.getElementById("myLinks"); //使用事件委托,只需在DOM树中尽量最高的层次上添加一个事件处理程序 list.addEventListener("click", function(event){ var target = event.target; switch(target.id){ case "goSomewhere": location.href = "http://www.baidu.com"; break; case "doSomething": document.title = "look!"; break; case "sayHi": alert("Hi!"); break; } }, false);
此外,还可以在不需要时移除事件处理程序。当带有事件处理程序的DOM被移除了(例如使用了removeChild()或innerHTML)则添加到该元素上的事件处理程序则无法被回收。因此,当你知道某个元素即将被移除的时候,那么最好手工移除该事件处理程序。并且在浏览器卸载页面之前(unonload)移除页面的所有事件处理程序。
btn.onclick=function(){ btn.onclick=null; ... })
六、模拟事件
模拟事件的三个步骤:1、创建event对象;2、初始化事件对象;3、触发事件。
var btn =document.getElementById("btn"); var event = document.createEvent("MouseEvents"); //创建event对象 event.initEvent("click",true,true); //初始化事件对象 btn.dispatchEvent(event); //触发事件