目录:
- 事件流
- 事件处理程序
- 事件对象
- 内存与性能
事件历史回顾:
- 事件最早是在 IE3 和 Netscape Navigator 2 中出现的,目地是把某些表单处理工作从服务器转移到浏览器上来。
- IE4 和 Netscape Navigator 3,这两家浏览器都提供了类似但又不同的API,而且持续了好几代。
- DOM2 以符合逻辑的方式来标准化 DOM 事件 API。目前所有现代浏览器都实现了 DOM2 Events 的核心部分。
- IE8 是最后一个使用专有事件系统的主流浏览器。
一、事件流:
事件流描述了页面接收事件的顺序。
1. IE 和 Netscape 开发团队提出了几乎完全相反的事件流方案:
- IE 将支持事件冒泡流
- Netscape Communicator 将支持事件捕获流。
- 由于旧版本浏览器不支持,因此实际当中几乎不会使用事件捕获。通常使用事件冒泡。
2. 事件流详情:
- 事件冒泡:从最具体的元素向上传播...
- 事件捕获:最不具体的节点 最先收到事件,最具体...后..
- DOM事件流:DOM2 Events 规范规定事件流分为 3 个阶段:事件捕获、到达目标和事件冒泡。
二、事件处理程序
1. HTML 事件处理程序
<script>
function showMessage() {
console.log("Hello world!");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()"/>
作为事件处理程序执行的代码可以访问全局作用域中的一切。
2. DOM0 事件处理程序
第四代 Web 浏览器中开始支持的事件处理程序赋值方法,直到现在所有现代浏览器仍然都支持此方法,主要原因是简单。
let btn = document.getElementById("myBtn");
btn.onclick = function() {
console.log(this.id); // "myBtn"
};
- this 引用元素本身
- 通过 this 可以访问元素的任何属性和方法。
- 以这种方式添加事件处理程序是注册在事件流的冒泡阶段。
- 若想移除 DOM0 方式添加的事件处理程序,可将事件处理程序属性的值设置为 null:
btn.onclick = null; // 移除事件处理程序
3. DOM2 事件处理程序
DOM2 Events 为事件处理程序的赋值和移除定义了两个方法:
- addEventListener()
- removeEventListener()
这两方法接收 3 个参数:事件名、事件处理函数和一个布尔值,true 表示在捕获阶段调用事件处理程序,false(默认值)表示在冒泡阶段调用事件处理程序。
let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
console.log(this.id);
}, false);
DOM2 Events 的主要特点有:
- DOM2 Events 主要优势是:可以为同一个事件添加多个事件处理程序
- 多个事件处理程序以添加顺序来触发
- 通过 addEventListener()添加的事件处理程序只能使用 removeEventListener()并传入与添加时同样的参数来移除。这意味着使用 addEventListener()添加的匿名函数无法移除
- 大多数情况下,事件处理程序会被添加到事件流的冒泡阶段,主要原因是跨浏览器兼容性好。
let btn = document.getElementById("myBtn");
btn.addEventListener("click", () => {
console.log(this.id);
}, false);
btn.removeEventListener("click", function() { // 没有效果!
console.log(this.id);
}, false);
4. IE 事件处理程序
IE 实现了与 DOM 类似的方法:
- attachEvent()
- detachEvent()
这两个方法接收两个同样的参数:事件处理程序的名字和事件处理函数。
IE Events 的主要特点:
-
attachEvent()的第一个参数是"onclick",DOM2 Events中是'click'
-
事件处理程序的作用域:
(1) 使用 DOM0方式时,事件处理程序中的 this 值等于目标元素。
(2) 使用 attachEvent()时,事件处理程序是在全局作用域中运行的,因此 this 等于 window。
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function() {
console.log(this === window); // true
});
- 同DOM2 Events 也可以给一个元素添加多个事件处理程序。但事件处理程序会以添加它们的顺序 反向触发
- 作为事件处理程序添加的匿名函数也无法移除。
5. 跨浏览器 事件处理程序
要确保事件处理代码具有最大兼容性,只需要让代码在冒泡阶段运行即可。
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;
}
}
};
优点:这两个方法已经实现了跨浏览器添加和移除事件处理程序;
缺点:
- 没有解决所有跨浏览器一致性问题,比如 IE的作用域问题、多个事件处理程序执行顺序问题等。
- 注意:DOM0 只支持给一个事件添加一个处理程序。好在 DOM0 浏览器已经很少有人使用了,所以影响应该不大。
三、事件对象
DOM 中发生事件时,所有相关信息都会被收集并存储在一个名为 event 的对象中。比如导致事件的元素、发生的事件类型,以及可能与特定事件相关的任何其他数据。
1. DOM 事件对象
- DOM0 或 DOM2 指定事件处理程序,都会传入这个 event 对象。
let btn = document.getElementById("myBtn");
btn.onclick = function(event) {
console.log(event.type); // "click"
};
btn.addEventListener("click", (event) => {
console.log(event.type); // "click"
}, false);
- 在事件处理程序内部,this 对象始终等于 currentTarget 的值,而 target 是触发事件的实际目标。
- preventDefault() 方法 用于阻止特定事件的默认动作。前提是 事件对象的 cancelable 属性为true。
- stopPropagation() 方法 用于立即阻止事件流在 DOM 结构中传播,取消后续的事件捕获或冒泡。
- eventPhase 表示调用事件处理程序的阶段:1 代表捕获阶段,2 代表到达目标,3 代表冒泡阶段
2. IE 事件对象
属性/方法 | 类型 | 读写 | 说明 |
---|---|---|---|
cancelBubble | 布尔值 | 读/写 | 默认为 false,设置为 true 可以取消冒泡(与 DOM 的 stopPropagation()方法相同) |
returnValue | 布尔值 | 读/写 | 默认为 true,设置为 false 可以取消事件默认行为(与 DOM 的 preventDefault()方法相同) |
srcElement | 元素 | 只读 | 事件目标(与 DOM 的 target 属性相同) |
type | 字符串 | 只读 | 触发的事件类型 |
- 若事件处理程序是使用 DOM0 方式指定的,则 event 对象只是 window 对象的一个属性
var btn = document.getElementById("myBtn");
btn.onclick = function() {
let event = window.event;
console.log(event.type); // "click"
};
- 若事件处理程序是使用 attachEvent()指定的,则 event对象 (window对象属性) 会作为唯一的参数传给处理函数
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(event) {
console.log(event.type); // "click"
});
-
this 值并不总是等于事件目标,因此推荐使用事件对象的 srcElement 属性代替 this。
(1) 使用 DOM0方式时,事件处理程序中的 this 值等于目标元素。
(2) 使用 attachEvent()时,事件处理程序是在全局作用域中运行的,因此 this 等于 window。
var btn = document.getElementById("myBtn");
// DOM 0
btn.onclick = function() {
console.log(window.event.srcElement === this); // true
};
// IE 的 attachEvent
btn.attachEvent("onclick", function(event) {
console.log(event.srcElement === this); // false
});
3. 跨浏览器 事件对象
var EventUtil = {
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;
}
},
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;
}
}
};
使用:
btn.onclick = function(event) {
// 获取事件对象
let event = EventUtil.getEvent(event);
// 获取事件目标
let target = EventUtil.getTarget(event);
// 取消默认..
EventUtil.preventDefault(event);
// 取消冒泡
EventUtil.stopPropagation(event);
};
四、内存与性能
- JavaScript 中,页面中事件处理程序的数量与页面整体性能直接相关。原因:
- 每个函数都是对象,都占用内存空间,对象越多,性能越差。
- 在指定事件处理程序中访问所需 DOM 会先期造成整个页面交互的延迟。
- 因此围绕着事件这块,若想优化内存与性能问题,可以:
- 限制一个页面中事件处理程序的数量,因为它们会占用过多内存,导致页面响应缓慢;
- 利用事件冒泡,事件委托可以解决限制事件处理程序数量的问题;
- 最好在页面卸载之前 删除 所有事件处理程序。
1. 事件委托
“过多事件处理程序”的解决方案是使用事件委托。
事件委托就是利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。如:给所有元素共同的祖先节点添加一个事件处理程序。
事件委托具有如下优点:
- document 对象随时可用,任何时候都可以给它添加事件处理程序(不用等待 DOMContentLoaded或 load 事件)。这意味着只要页面渲染出可点击的元素,就可以无延迟地起作用。
- 节省 花在设置页面事件处理程序上的时间。只指定一个事件处理程序既可以节省 DOM 引用,也可以节省时间。
- 减少整个页面所需的内存,提升整体性能。
2. 删除事件处理程序
-
待删除的元素上如果有事件处理程序,就不会被垃圾收集程序正常清理。因此最好在删除DOM前手工删除它的事件处理程序。
-
在页面卸载后事件处理程序没有被清理,则它们仍然会残留在内存中。之后,浏览器每次加载和卸载页面(比如通过前进、后退或刷新),内存中残留对象的数量都会增加。因此在页面卸载前 最好先删除所有事件处理程序。