一、 0 级 DOM 上的事件和 2 级 DOM 事件机制
0 级 DOM 上的事件又称原始事件模型,所有的浏览器都支持他,而且是通用的。 2 级 DOM 事件机制又为标准事件模型,除了 ie 其他浏览器都支持( ie9 据说也支持,有待考证), ie 虽然大部分与标准事件模型一样,但有自己专有的事件模型,因此开发人员要实现标准事件模型必须为 IE 写特定的代码,这给程序员增加了负担。
原始事件模型
1.<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 2.<html> 3. <head> 4. <title>浏览器0级DOM上的事件</title> 5. <meta http-equiv="content-type" content="text/html; charset=UTF-8"> 6. </head> 7. 8. <body> 9. <input type="button" value = "点击我" id = "btn"> 10. </body> 11.</html> 12. 13.<script type="text/javascript"> 14.<!-- 15.var method1 = function(){alert(1)}; 16.var method2 = function(){alert(2)}; 17.var method3 = function(){alert(3)}; 18.document.getElementById("btn").onclick = method1; 19.document.getElementById("btn").onclick = method2; 20.document.getElementById("btn").onclick = method3; 21.//--> 22.</script>
以上书写在各浏览器中都是兼容的,但只有 medhot3 被执行,即同一个对象同一类型的事件只能注册一个处理函数,要想实现注册多个处理函数,需要利用 2 级 DOM 事件机制。
1.<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 2.<html> 3. <head> 4. <title>浏览器2级DOM事件机制</title> 5. <meta http-equiv="content-type" content="text/html; charset=UTF-8"> 6. </head> 7. 8. <body> 9. <input type="button" value = "点击我" id = "btn"> 10. </body> 11.</html> 12. 13.<script type="text/javascript"> 14.<!-- 15.var method1 = function(){alert(1)}; 16.var method2 = function(){alert(2)}; 17.var method3 = function(){alert(3)}; 18. 19.//执行顺序为method1->method2->method3 20.//标准事件模型 21.var btn1Obj = document.getElementById("btn"); 22.btn1Obj.addEventListener("click",method1,false); 23.btn1Obj.addEventListener("click",method2,false); 24.btn1Obj.addEventListener("click",method3,false); 25. 26.//执行顺序为method3->method2->method1 27.//IE事件模型 28.var btn1Obj = document.getElementById("btn"); 29.btn1Obj.attachEvent("onclick",method1); 30.btn1Obj.attachEvent("onclick",method2); 31.btn1Obj.attachEvent("onclick",method3); 32. 33.//--> 34.</script>
从运行结果来看, ie 和 firefox 下执行的顺序是不一样的
二、2级DOM事件模型事件的注册与删除
element.addEventListener(eventType,fn,useCapture); // 注册事件
element.removeEventListener(eventType,fn, useCapture);// 删除事件
可以用 addEventListener() 给同一个对象同一类型的事件注册多个处理函数,但是如果在同一元素上多次注册了一个处理函数,那么第一次注册后的所有注册都将被忽略,但删除该注册函数(调用 removeEventListener() )后可以再重新注册该函数。需要注意的是删除事件, useCapture 的值必须要跟注册时保持一致
1.<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 2. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 3.<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN"> 4.<head> 5.<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 6.<title>浏览器事件机制——注册和删除事件</title> 7.<style> 8. div {border:1px solid blue;} 9. div#div1 {padding:40px;background-color:#aaaaaa;} 10. div#div2 {padding:40px;background-color:#bbbbbb;} 11. div#div3 {padding:40px;background-color:#cccccc;} 12.</style> 13.</head> 14. <body> 15.<div id="div1" style="width:100px;height:100px;" > 16. 我是老大, 点击我添加老三的click事件 17.</div> 18.<br/> 19.<div id="div2" style="width:100px;height:110px;" > 20. 我是老二, 点击我删除老三的click事件 21.</div> 22.<br/> 23.<div id="div3" style="width:100px;height:100px;" > 24. 我是老三,是否有click事件,老大老二说了算,呵呵 25.</div> 26.<script> 27. function click1() { 28. alert("I am div1,add div3 event"); 29. if(window.addEventListener){ 30. div3.addEventListener("click", click3, false); 31. }else if (window.attachEvent){ 32. div3.attachEvent("onclick", click3); 33. } 34. } 35. function click2() { 36. alert("I am div2,remove div3 event"); 37. if(window.addEventListener){ 38. div3.removeEventListener("click", click3, false); 39. }else if (window.attachEvent){ 40. div3.detachEvent("onclick", click3); 41. } 42. } 43. function click3() { 44. alert("I am div3"); 45. } 46. 47. var div1 = document.getElementById("div1"); 48. var div2 = document.getElementById("div2"); 49. var div3 = document.getElementById("div3"); 50. 51. if(window.addEventListener){ 52. div1.addEventListener("click", click1, false); 53. div2.addEventListener("click", click2, false); 54. }else if (window.attachEvent){ 55. div1.attachEvent("onclick", click1); 56. div2.attachEvent("onclick", click2); 57. } 58.</script> 59.</body> 60.</html>
三、2级DOM事件冒泡模型(Bubble Model)
在2级DOM事件模型中,事件传播分三个阶段进行,即捕获阶段(capturing)、目标阶段和冒泡阶段(bubbling)。在捕获阶段,事件从Document对象沿着文档树向下传播给目标节点,如果目标的任何一个祖先(不是目标本身)专门注册了捕获事件句柄,那么在事件传播过程中,就会运行这些句柄,在冒泡阶段,事件将从目标元素向上传播回或气泡回Document对象的文档层次。虽然所有事件都受事件传播的捕获阶段的支配,但并非所有类型的事件都起泡。
在注册事件时,useCapture参数确定侦听器是运行于捕获阶段、目标阶段还是冒泡阶段。 如果将 useCapture 设置为 true,则侦听器只在捕获阶段处理事件,而不在目标或冒泡阶段 处理事件。 如果useCapture 为 false,则侦听器只在目标或冒泡阶段处理事件。 要在所有三个阶段都侦听事件,需调用两次 addEventListener,一次将 useCapture 设置为 true,第二次再将useCapture 设置为 false。
1.<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 2. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 3.<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN"> 4.<head> 5.<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 6.<title>浏览器事件机制——冒泡处理</title> 7.<style> 8. div {border:1px solid blue;} 9. div#divGrandpa {padding:40px;background-color:#aaaaaa;} 10. div#divFather {padding:40px;background-color:#bbbbbb;} 11. div#divSon {padding:40px;background-color:#cccccc;} 12.</style> 13.</head> 14. <body> 15.<div id="divGrandpa" style="width:300px;height:200px;" > 16. <div id="divFather" style="width:200px;height:120px;" > 17. <div id="divSon" style="width:100px;height:40px;" > 18. 点击我 19. </div> 20. </div> 21.</div> 22.<script> 23. function showSon() { 24. alert("I am son"); 25. } 26. function showFather() { 27. alert("I am father"); 28. } 29. function showGrandpa() { 30. alert("I am Grandpa"); 31. } 32. var grandpa = document.getElementById("divGrandpa"); 33. var father = document.getElementById("divFather"); 34. var son = document.getElementById("divSon"); 35. if(window.addEventListener){ 36. grandpa.addEventListener("click", showGrandpa, false); 37. father.addEventListener("click", showFather, false); 38. son.addEventListener("click", showSon, false); 39. }else if (window.attachEvent){ 40. grandpa.attachEvent("onclick", showGrandpa); 41. father.attachEvent("onclick", showFather); 42. son.attachEvent("onclick", showSon); 43. } 44.</script> 45.</body> 46.</html>
从运行结果来看,对于ie,在ie(ie8之前的版本,包括ie8)中当点击son节点时,会分别弹出I am son、I am father和I am Grandpa,即事件最先被底层的结点触发,再逐渐上传,直到最外层的结点,冒泡方式为儿子——>父亲的模式;在Firefox等支持标准事件模型的浏览器中,跟addEventListener的Capture参数有关,当设置为true时,为捕获模式,事件会从最顶层的结点往下传输,即 父亲——>儿子的传播模式。当设为false(默认值)时,则会按冒泡模式传递事件。另外由于ie9即支持window.attachEvent,又支持window.addEventListener,所以会根据代码的书写来运行其效果的。
四、如何停止事件的传递
在IE浏览器中可以调用以下代码
event.cancelBubble = true;
在Firefox等遵循W3C规范的浏览器中,可以调用以下代码
e.stopPropagation();
调用以上代码后可以终止事件在传播过程的捕获、目标处理或起泡阶段进一步传播。调用该方法后,该节点上处理该事件的处理程序将被调用,事件不再被分派到其他节点(即不再进一步传播)。
该方法(属性)将停止事件的传播,阻止它被分派到其他 Document 节点。在事件传播的任何阶段都可以调用它。注意,虽然该方法不能阻止同一个 Document 节点上的其他事件句柄被调用,但是它可以阻止把事件分派到其他节点。
1.<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 2. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 3.<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN"> 4.<head> 5.<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 6.<title>浏览器事件机制——停止事件的进一步传递</title> 7.<style> 8. div {border:1px solid blue;} 9. div#divGrandpa {padding:40px;background-color:#aaaaaa;} 10. div#divFather {padding:40px;background-color:#bbbbbb;} 11. div#divSon {padding:40px;background-color:#cccccc;} 12.</style> 13.</head> 14. <body> 15.<div id="divGrandpa" style="width:300px;height:200px;" > 16. <div id="divFather" style="width:200px;height:120px;" > 17. <div id="divSon" style="width:100px;height:40px;" > 18. 点击我 19. </div> 20. </div> 21.</div> 22. 23.<script> 24. function showSon(e) { 25. alert("I am son"); 26. } 27. function showFather(e) { 28. //IE把event对象作为window对象的一个属性,而W3C把event对象作为处理程序的一个参数 29. ee = e || event; 30. if(e.stopPropagation){ 31. e.stopPropagation(); 32. }else{ 33. e.cancelBubble = true; 34. } 35. alert("I am father"); 36. } 37. function showGrandpa(e) { 38. alert("I am Grandpa"); 39. } 40. var grandpa = document.getElementById("divGrandpa"); 41. var father = document.getElementById("divFather"); 42. var son = document.getElementById("divSon"); 43. if(window.addEventListener){ 44. grandpa.addEventListener("click", showGrandpa, false); 45. father.addEventListener("click", showFather, false); 46. son.addEventListener("click", showSon, false); 47. }else if (window.attachEvent){ 48. grandpa.attachEvent("onclick", showGrandpa); 49. father.attachEvent("onclick", showFather); 50. son.attachEvent("onclick", showSon); 51. } 52.</script> 53.</body> 54.</html>
五、自定义事件
1、不带参数事件处理,也是最简单的事件设计模式
最简单的一种模式是将一个类的方法成员定义为事件,通常是一个空函数,当程序需要处理该事件时,再进行扩充该事件接口。比如:
1.function Class1(){ 2. //构造函数 3. } 4. Class1.prototype = { 5. show : function(){ 6. this.onShow();//触发onShow事件 7. }, 8. onShow : function(){}//定义事件接口 9. } 10. //创建class1实例 11. var obj = new Class1(); 12. //创建obj的onShow事件处理程序 13. obj.onShow = function(){ 14. alert(‘onshow event‘); 15. } 16. //调用obj的show方法 17. obj.show();
2、给事件处理程序传递参数
1.//将有参数的函数封装为无参数的函数 2. function createFunction(obj, strFn){ 3. obj = obj || window; 4. var args = []; 5. for(var i = 2; i < arguments.length; i++){ 6. args.push(arguments[i]); 7. } 8. return function(){ 9. //该语句相当于obj[strFn](args[0],args[1],...); 10. obj[strFn].apply(obj,args); 11. } 12. } 13. //定义类 Class1 14. function Class1(){ 15. //构造函数 16. } 17. Class1.prototype = { 18. show : function(){ 19. this.onShow();//触发onShow事件 20. }, 21. onShow : function(){}//定义事件接口 22. } 23. //创建class1实例 24. var obj = new Class1(); 25. //创建obj的onShow事件处理程序 26. function objOnShow(userName){ 27. alert(‘hello, ‘ + userName); 28. } 29. var userName = ‘xiaowang‘; 30. //绑定obj的onShow事件 31. obj.onShow = createFunction(null,‘objOnShow‘,userName); 32. //调用obj的show方法 33. obj.show();
在以上代码中,将变量userName作为参数传递给了objOnShow事件处理程序。事实上,obj.onShow 得到的事件处理程序并不是objOnShow,而是由createFunction返回的一个无参函数
3、自定义事件支持多绑定
1.//定义类 Class1 2. function Class1(){ 3. //构造函数 4. } 5. Class1.prototype = { 6. show : function(){ 7. //如果有事件绑定则循环onshow数组,触发该事件 8. if(this.onshow){ 9. for(var i = 0, len = this.onshow.length; i < len; i++){ 10. this.onshow[i]();//调用事件处理程序 11. } 12. } 13. }, 14. addEventOnShow : function (_eHandler){ 15. this.onshow = this.onshow || [];//用数组存储绑定的事件处理程序引用 16. this.onshow.push(_eHandler); 17. } 18. } 19. //创建class1实例 20. var obj = new Class1(); 21. //事件一 22. function onShow1(){ 23. alert(‘event1‘); 24. } 25. //事件二 26. function onShow2(){ 27. alert(‘event2‘); 28. } 29. //绑定事件 30. obj.addEventOnShow(onShow1); 31. obj.addEventOnShow(onShow2); 32. //调用obj的show方法 33. obj.show();
4、自定义事件支持带参数的多绑定
1.//将有参数的函数封装为无参数的函数 2. function createFunction(obj, strFn){ 3. obj = obj || window; 4. var args = []; 5. for(var i = 2; i < arguments.length; i++){ 6. args.push(arguments[i]); 7. } 8. return function(){ 9. //该语句相当于obj[strFn](args[0],args[1],...); 10. obj[strFn].apply(obj,args); 11. } 12. } 13. //定义类 Class1 14. function Class1(){ 15. //构造函数 16. } 17. Class1.prototype = { 18. show : function(){ 19. //如果有事件绑定则循环onshow数组,触发该事件 20. if(this.onshow){ 21. for(var i = 0, len = this.onshow.length; i < len; i++){ 22. this.onshow[i]();//调用事件处理程序 23. } 24. } 25. }, 26. addEventOnShow : function (_eHandler){ 27. this.onshow = this.onshow || [];//用数组存储绑定的事件处理程序引用 28. this.onshow.push(_eHandler); 29. } 30. } 31. //创建class1实例 32. var obj = new Class1(); 33. //创建obj的onShow事件处理程序 34. function objOnShow(userName){ 35. alert(‘hello, ‘ + userName); 36. } 37. //事件一 38. var userName1 = ‘xiaowang‘; 39. var onShow1 = createFunction(null,‘objOnShow‘,userName1); 40. //事件一 41. var userName2 = ‘xiaoli‘; 42. var onShow2 = createFunction(null,‘objOnShow‘,userName2); 43. //绑定事件 44. obj.addEventOnShow(onShow1); 45. obj.addEventOnShow(onShow2); 46. //调用obj的show方法 47. obj.show();
以上实现把带参数和多绑定结合在一起,还可以增加一个removeEventOnShow来删除已注册的事件。
六、把对象注册为事件句柄
在编写面向对象的JavaScript程序时,如果想用对象作为事件句柄,那么可以使用如下的函数来注册它们:
function registerObjectEventHandler(element,eventtype,listener,captures){ element.addEventListener(eventtype, function(event) {listener.handleEvent(event);},captures); }
用这个函数可以把任何对象注册为事件句柄,只要它定义了handleEvent()方法。Firefox(以及其他基于Mozilla代码的浏览器)允许直接把定义了handleEvent()方法的事件监听器对象传递给addEventListener()方法而不是函数引用。对于这些浏览器来说,不需要我们刚才给出的特殊注册函数。
请看下面的例子
1.<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 2.<html> 3. <head> 4. <title>把对象注册为事件句柄</title> 5. <meta http-equiv="content-type" content="text/html; charset=UTF-8"> 6. </head> 7. 8. <body> 9. <input type="button" value = "点击我" id = "btn"> 10. </body> 11.</html> 12. 13.<script type="text/javascript"> 14.<!-- 15. var EventHandler = function(){} 16. EventHandler.prototype.handleEvent = function(event){ 17. alert(‘用对象作为事件句柄,只要实现该对象的方法handleEvent即可‘); 18. alert(event.type); 19. } 20. var objectHandler = new EventHandler(); 21. var btn1Obj = document.getElementById("btn"); 22. if(window.addEventListener){ 23. btn1Obj.addEventListener("click",objectHandler,false); 24. }else if (window.attachEvent){ 25. //btn1Obj.attachEvent("onclick",objectHandler);//调用失败,说明不支持把对象注册为事件句柄 26. //btn1Obj.attachEvent("onclick",objectHandler.handleEvent); 27. registerObjectEventHandler(btn1Obj,"onclick",objectHandler); 28. } 29. 30. /** 31. * 对于不支持把对象注册为事件句柄的浏览器,可以调用以下方法来实现 32. */ 33. function registerObjectEventHandler(element,eventtype,listener,captures){ 34. if(window.addEventListener){ 35. element.addEventListener(eventtype, 36. function(event) {listener.handleEvent(event);},captures); 37. }else if (window.attachEvent){ 38. element.attachEvent(eventtype, 39. function(event) {listener.handleEvent(event);}); 40. } 41. } 42. 43.//--> 44.</script>
浏览器事件机制与自定义事件的实现