事件:文档或浏览器窗口中发生的一些特定的交互瞬间,也即用户或浏览器自身执行的某种动作。
-----------------------------------------------------------------------------------------------------------
一、事件流
事件流:描述的是页面中接收事件的顺序。
IE:事件冒泡流,即事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点(文档);
NetScape:事件捕获流,即不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。其用意在于在事件达到预定目标之前捕获它。
DOM事件流:
(1)事件捕获阶段:建立了传播路径,为截获事件提供了机会
(2)处于目标阶段:目标接收到事件,并触发;
(3)事件冒泡阶段:在这个阶段对事件做出响应,事件会通过这个路径回溯到文档根节点。
在 DOM 事件流中,实际的目标(div元素)在捕获阶段不会接收到事件。
- 这意味着在捕获阶段,事件从 document 到 <html> 再到 <body> 后就停止了。
- 下一阶段是“处于目标”阶段,于是事件在 <div> 上发生,并在事件处理中被看成冒泡阶段的一部分。
- 然后,冒泡阶段发生,事件又传播回文档。
*多数支持 DOM 事件流的浏览器都实现了一种特定的行为;即使“DOM2级事件“规范明确要求捕获阶段不会涉及事件目标,但各种浏览器的高版本都会在不惑阶段触发事件对象上的事件。结果,就是有两个机会在目标对象上面操作事件。
-----------------------------------------------------------------------------------------------------------
二、事件处理程序
事件处理程序(事件侦听器):响应某个事件的函数。
事件处理程序的名字以 "on" 开头。如 click、load(事件)—> onclick、onload(事件处理程序)
1. HTML 事件处理程序
——this 值等于事件的目标元素
使用与相应事件处理程序同名的 HTML 特性来指定
<input type="button" value="Click me" onclick="alert(event.type)">
优点 |
(1)有权访问全局作用域中的任何代码;
(2)会封装着元素属性值的函数,这个函数中有局部变量 event,即事件对象;
(3)在这个函数内部,this 值等于事件的目标元素; (4)扩展作用域。在这个函数内部,可以像访问局部变量一样访问 document 及该元素本身的成员;可以让事件处理程序无需引用表单元素就能访问其他表单字段。 |
缺点
|
(1)存在时差问题;
(2)这样扩展事件处理程序的作用域链在不同浏览器中会导致不同结果;
(3)HTML 与 JavaScript 代码紧密耦合。
|
删除事件处理程序
|
将相应的属性设置为 nul
|
2. DOM0 级事件处理程序
——元素的方法(事件处理程序在元素的作用域中运行,this 引用当前元素)
方法
|
(1)首先,必须取得一个要操作的对象的引用;
(2)将一个函数赋值给一个事件处理程序属性
|
缺点
|
对每个事件只支持一个事件处理程序
|
删除事件处理程序
|
将事件处理程序属性设置为 null
|
事件处理阶段
|
在事件流的冒泡阶段被处理
|
每个元素(包括 window 和 document)都有自己的事件处理程序属性(通常全部小写,如 onclick)。
var btn = document.getElementById("myBtn"); btn.onclick = function() { alert(this.id); // "myBtn" }; btn.onclick = null; // 删除事件处理程序
3. DOM2 级事件处理程序
——在其依附的元素的作用域中运行
方法
|
(1)指定事件处理程序:addEventListener()
(2)删除事件处理程序:removeEventListener()
|
方法的参数
|
要处理的事件名
作为事件处理程序的函数
布尔值(true 表示捕获阶段调用事件处理程序,false 表示冒泡阶段调用事件处理程序)
|
好处
|
可以添加多个事件处理程序
|
移除事件处理程序 |
只能通过 removeEventListener() 来移除;
移除时传入的参数与添加处理程序时使用的参数相同,使用匿名函数无法移除。
|
事件处理阶段
|
大多数情况下,都是将事件处理程序添加到事件流的冒泡阶段;
如果不是特别需要,不建议在事件捕获阶段注册事件处理程序。
|
var btn = document.getElementById("myBtn"); var handler = function() { alert(this.id); }; btn.addEventListener("click", handler, false); btn.removeEventListener("click", handler, false); // 删除事件处理程序
4. IE 事件处理程序
方法 |
(1)attachEvent()
(2)detachEvent()
|
方法的参数
|
事件处理程序名称、事件处理程序函数
|
好处
|
可以添加多个事件处理程序,同 addEventListener()
|
移除事件处理程序
|
通过 detachEvent() 来移除,必须提供相同的参数,同 DOM 方法
|
与 DOM0 级方法区别
|
主要区别在于事件处理程序的作用域:
DOM0 级方法:事件处理程序会在其所属元素的作用域内运行;
attachEvent() 方法:事件处理程序会在全局作用域中运行,因此 this 等于 window。
|
var btn = document.getElementById("myBtn"); btn.attachEvent("onclick", function() { alert(this === window); // true });
5. 跨浏览器的事件处理程序
对象
|
EventUtil(用于处理浏览器间的差异)
|
方法 |
addHandler():视情况分别使用 DOM0 级方法、DOM2 级方法或 IE 方法来添加事件
removeHandler():移除添加的事件处理程序
|
参数 |
要操作的元素、事件名称、事件处理程序函数
|
缺点 |
没有考虑到所有的浏览器问题,例如在 IE 中的作用域问题。
不过,使用它们添加和移除事件处理程序还是足够了。
|
1 // 定义EventUtil对象 2 var EventUtil = { 3 4 addHandler: function(element, type, handler) { 5 if (element.addEventListener) { 6 element.addEventListener(type, handler, false); 7 } else if (element.attachEvent) { 8 element.attachEvent("on" + type, handler); 9 } else { 10 element["on" + type] = handler; 11 } 12 }, 13 removeHandler: function(element, type, handler) { 14 if (element.removeEventListener) { 15 element.removeEventListener(type, handler, false); 16 } else if (element.detachEvent) { 17 element.detachEvent("on" + type, handler); 18 } else { 19 element["on" + type] = null; 20 } 21 } 22 } 23 24 // 使用EventUtil对象 25 var btn = document.getElementById("myBtn"); 26 var handler = function() { 27 alert("Clicked"); 28 } 29 30 EventUtil.addHandler(btn, "click", handler); 31 EventUtil.removeHandler(btn, "click", handler);
-----------------------------------------------------------------------------------------------------------
三、事件对象
事件对象:在触发DOM上的某个事件时,会产生一个事件对象 event,这个对象中包含着所有与事件有关的信息。包括导致事件的元素、事件的类型及其他与特定事件相关的信息。
(1)DOM 中的事件对象
兼容 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);
在通过 HTML 特性指定事件处理程序时,变量 event 中保存着 event 对象。
<input type="button" value="Click Me" onclick="alert(event.type)" />
event 对象包含与创建它的特定事件相关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都会有下表列出的成员。
属性/方法
|
类型
|
读/写
|
说明
|
bubbles
|
Boolean
|
只读
|
表明事件是否冒泡
|
cancelable
|
Boolean
|
只读
|
表明是否可以取消事件的默认行为
|
currentTarget
|
Element
|
只读
|
其事件处理程序当前正在处理事件的那个元素
|
defaultPrevented
|
Boolean
|
只读
|
为 true 表示已经调用了 preventDefault() (DOM3 级事件中新增)
|
detail
|
Integer
|
只读
|
与事件相关的细节信息
|
eventPhase
|
Integer
|
只读
|
调用事件处理程序的阶段:1 表示捕获阶段,2 表示“处于目标,3 表示冒泡阶段
|
preventDefault()
|
Function
|
只读 |
取消事件的默认行为。如果 cancelable 是 true,则可以使用该方法
|
stopImmediatePropagation()
|
Function
|
只读
|
取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用(DOM3 级事件中新增)
|
stopPropagation()
|
Function
|
只读
|
取消事件的进一步捕获或冒泡。如果 bubbles 为 true,则可以使用该方法
|
target
|
Element
|
只读
|
事件的目标
|
trusted
|
Boolean
|
只读
|
为 true 表示事件是浏览器生成的。为 false 表示事件由开发人员通过 JavaScript 创建的(DOM3 级事件中新增)
|
type
|
String
|
只读
|
被触发的事件的类型,需要通过一个函数处理多个事件时使用
|
view
|
AbstractView
|
只读
|
与事件关联的抽象视图。等同于发生事件的 window 对象
|
在事件处理程序内部,对象 this 始终等于 currentTarget 的值,而 target 则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则 this、currentTarget 和 target 包含相同的值。
尽管 "处于目标" 发生在冒泡阶段,但 eventPhase 仍然一直等于 2 。
只有在事件处理程序执行期间,event 对象才会存在;一旦事件处理程序执行完成,event 对象就会被销毁。
(2)IE 中的事件对象
与访问 DOM 中的 event 对象不同,要访问 IE 中的 event 对象有几种不同的方式,取决于指定事件处理程序的方法。
- 在使用 DOM0 级方法添加事件处理程序时,event 对象作为 window 对象的一个属性存在(window.event)。
btn.onclick = function() { var event = window.event; alert(event.type); // "click" };
- 如果事件处理程序是使用 attachEvent() 添加的,event 对象会作为参数被传入事件处理程序中。也可通过 window 对象来访问 event 对象,同 DOM0 级方法。
btn.attachEvent("onclick", function(event) { alert(event.type); // "click" });
- 如果是通过 HTML 特性指定的事件处理程序,可以通过一个名叫 event 的变量来访问 event 对象。
<input type="button" onclick="alert(event.type)" >
IE 的所有 event 对象都包含的属性和方法:
属性/方法
|
类型
|
读/写
|
说明
|
cancleBubble
|
Boolean
|
读/写
|
默认值为 false,但将其设置为 true 就可以取消事件冒泡
(同 DOM 中的 stopPropagation() 方法)
|
returnValue
|
Boolean
|
读/写
|
默认值为 true,但将其设置为 false 就可以取消事件的默认行为
(同 DOM 中的 preventDefault() 方法)
|
srcElement
|
Element
|
只读
|
事件的目标(同 DOM 中的 target 属性)
|
type
|
String
|
只读
|
被触发的事件的类型
|
因为事件处理程序的作用域是根据指定它的方式来确定的,所以不能认为 this 会始终等于事件目标。故而,最好还是使用 event.srcElement 比较保险
(3)跨浏览器的事件对象
对象
|
EventUtil(用于处理浏览器间的差异)
|
方法 | getEvent():返回对 event 对象的引用 getTarget():返回事件的目标 preventDefault():用于取消事件的默认行为 stopPropagation():阻止事件流 |
1 // 定义EventUtil对象 2 var EventUtil = { 3 4 addHandler: function(element, type, handler) { 5 if (element.addEventListener) { 6 element.addEventListener(type, handler, false); 7 } else if (element.attachEvent) { 8 element.attachEvent("on" + type, handler); 9 } else { 10 element["on" + type] = handler; 11 } 12 }, 13 14 getEvent: function(event) { 15 return event ? event : window.event; 16 }, 17 18 getTarget: function(event) { 19 return event.targete || event.srcElement; 20 }, 21 22 preventDefault: function(event) { 23 if (event.preventDefault) { 24 event.preventDefault(); 25 } else { 26 event.retrunValue = false; 27 } 28 }, 29 30 removeHandler: function(element, type, handler) { 31 if (element.removeEventListener) { 32 element.removeEventListener(type, handler, false); 33 } else if (element.detachEvent) { 34 element.detachEvent("on" + type, handler); 35 } else { 36 element["on" + type] = null; 37 } 38 }, 39 40 stopPropagation: function(event) { 41 if (event.stopPropagation) { 42 event.stopPropagation(); 43 } else { 44 event.cancleBubble = true; 45 } 46 } 47 } 48 49 // 使用EventUtil对象 50 var btn = document.getElementById("myBtn"); 51 var handler = function() { 52 alert("Clicked"); 53 } 54 55 EventUtil.addHandler(btn, "click", handler); 56 57 btn.onclick = function(event) { 58 event.EventUtil.getEvent(event); 59 var target = EventUtil.getTarget(event); 60 EventUtil.preventDefault(event); 61 EventUtil.stopPropagation(event); 62 } 63 64 EventUtil.removeHandler(btn, "click", handler);
-----------------------------------------------------------------------------------------------------------
四、事件类型
-----------------------------------------------------------------------------------------------------------
五、内存和性能
添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能。
原因:
(1)每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差;
(2)必须事先指定所有事件处理程序而导致的 DOM 访问次数,会延迟整个页面的交互就绪时间。
解决方案:
(1)事件委托(限制连接的数量):利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件
每当将事件处理程序指定给元素时,运行中的浏览器代码与支持页面交互的 JavaScript 代码之间就会建立一个连接。这种连接越多,页面执行起来就越慢。事件委托限制了连接的数量。
使用事件委托,只需在 DOM 树中尽量最高的层次上添加一个事件处理程序。
如果可行的话,也可以考虑为 document 对象添加一个事件处理程序,用以处理页面上发生的某种特定类型的事件。具有以下优点:
- document对象很快就可以访问到,而且可以在页面生命周期的任何时点为上为它添加事件处理程序(无需等待 DOMContentLoaded 或 load 事件)
- 在页面中设置事件处理程序所需的时间更少
- 整个页面占用的内存空间更少,能够提升整体性能
所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术。最适合的事件包括 click、mousedown、mouseup、keydown、keyup 和 keypress,mouseover 和 mouseout 不适合,因为处理不容易。
(2)移除事件处理程序:
在内存中留有那些过时不用的“空事件处理程序”,也是造成 Web 应用程序内存与性能问题的主要原因。
在事件处理程序中删除按钮也能阻止事件冒泡。目标元素在文档中是事件冒泡的前提。
导致“空事件处理程序”的情况:
- 使用 removeChild()、replaceChild() 或 innerHTML 等 DOM 操作删除元素;——>手工移除事件处理程序
- 卸载页面的时候。——>在卸载页面之前,先通过 onunload 事件处理程序移除所有事件处理程序
采用事件委托有助于解决“空事件处理程序”的问题。优势:
- 如果事先知道将来有可能使用 innerHTML 替换掉页面中的一部分,那么就可以不直接把事件处理程序添加到该部分的元素中。而通过把事件处理程序指定给较高层次的元素,同样能够处理该区域中的事件。
- 需要跟踪的事件处理程序越少,移除它们就越容易
参考博文: