事件
JavaScript 和 HTML 的交互是通过事件实现的。JavaScript 采用异步事件驱动编程模型,当文档、浏览器、元素或与之相关对象发生特定事情时,浏览器会产生事件。我们可以给这个事件注册相应的事件处理程序,事件处理程序也就是事件发生后的处理动作。
事件流
事件流描述的是从页面中接收事件的顺序。
DOM2 级事件规定事件流包含三个阶段
1.document 往事件触发地点,捕获前进,遇到相同注册事件立即触发执行 2.到达事件位置,触发事件,如果该处既注册了冒泡事件,也注册了捕获事件,按照注册顺序执行 3.事件触发地点往 document 方向,冒泡前进,遇到相同注册事件立即触发
IE8 及 IE8 以下不支持事件捕获,只支持事件冒泡,早期 Netscape 只支持事件捕获,现代浏览器采用 DOM2 级事件流模型。
事件处理程序
响应某个事件的函数叫做事件处理程序或事件侦听器(listener),事件处理程序的名字以“on”开头。
事件对象
在触发 DOM 上某个事件时,会产生一个事件对象 event,它包含着所有与事件有关的信息。只有在事件处理程序执行期间 event 对象才会存在,执行完毕后 event 对象被销毁。
DOM 中的事件对象:
无论指定事件处理程序时使用了哪种方法,兼容 DOM 的浏览器都会将一个 event 对象传入到事件处理程序中。
HTML 方法添加时,变量 event 中保存着 event 对象。
event 对象通用的属性和方法中比较常用的有:
currentTarget:其事件处理程序当前正处理事件的元素
target:事件的目标
preventDefault():阻止事件的默认行为
stopPropagation():阻止事件的进一步捕获或冒泡
type:被触发的事件类型
IE 中的事件对象:
DOM0 级方法添加事件处理程序时,event 对象是 window 的属性;
IE 方法添加时,event 对象会像 DOM 中一样,作为参数被传递到事件处理程序函数中。
HTML 方法添加时,和 DOM 相同,event 对象保存在变量 event 中。
常用方法和属性:
srcElement:等于 target,默认目标
returnValue:等于 preventDefault(),阻止默认行为
cancelBubble:设为 true 取消事件冒泡,等于 stopPropagation()
type:被触发的事件类型
跨浏览器的事件对象
虽然 DOM 和 IE 的 event 对象不同,但基于它们的相似性,我们还是可以写出跨浏览器的事件对象方案
function getEvent(e) {
return e || window.event;
}
function getTarget(e) {
return e.target || e.scrElement;
}
function preventDefault(e) {
if (e.preventDefault) e.preventDefault();
else e.returnValue = false;
}
function stopPropagation(e) {
if (e.stopPropagation) e.stopPropagation();
else e.cancelBubble = true;
}
事件模拟
DOM:
(1) 创建:用 document.createEvent()方法创建 event 对象,该方法接受一个参数,即要创建的事件类型的字符串。
(2) 初始化:不同类型的 event 有不同名字的初始化方法:
鼠标事件:”MouseEvents”,”initMouseEvent()”,接受 15 个参数
键盘事件:”KeyboardEvent”,”initKeyEvent()”,接受 10 个参数。键盘事件只有支持 DOM3 的浏览器才能用,要先检测。
其他事件:变动事件,HTML 事件,方法类似,很少使用。
自定义 DOM 事件:”CustonEvent”,”initCustonEvent()”,接受 4 个参数(type 字符串、bubbles 布尔值、cancelable 布尔值、detail 对象)。
(3) 触发:dispatchEvent()方法,接受一个参数,即要触发事件的 event 对象。
IE9+开始支持 DOM 方式。
IE:
(1) 创建:document.createEventObject()创建 event 对象。
(2) 初始化:用 event.propertyName = …的形式手动添加信息,没有特别的方法。
(3) 触发:fireEvent()方法,接受两个参数:事件处理程序的名称、event 对象。
绑定事件处理程序的方法
共有四种方法:HTML、DOM0、DOM2、IE,也可将后三种整合为跨浏览器方法。
(1) HTML:
对于不同元素,它支持的事件可以用一个与相应事件处理程序同名的 HTML attribute 来指定。如
<input type="button" value="click here" onclick="alert('hi')" />
两个缺点:从 HTML 元素出现到事件处理程序就绪之间存在时差;HTML 和 JS 代码紧密耦合。
(2) DOM0:
先取得一个对象的引用,再将一个函数赋值给该引用的一个事件处理程序属性。
var btn = document.getElementById('myBtn');
btn.onclick = function () {
alert('Clicked');
};
DOM0 是元素的方法,事件处理程序是在元素的作用域中运行,在冒泡阶段被处理。在代码运行到它们前不会指定事件处理程序,和 HTML 方法一样存在时差,可能某段时间内怎么点都没反应。
(3) DOM2:
绑定事件处理程序:addEventListener()
删除事件处理程序:removeEventListener()
三个参数:要处理的事件名、作为事件处理程序的函数、表示调用事件处理程序阶段的布尔值(true 捕获阶段,false 冒泡阶段,一般都设为 false 以保证兼容性)。
好处:可以添加多个事件处理程序(按添加他们的顺序触发)。
注意事项:通过 addEventListener()添加的匿名函数无法移除。
备注:IE9 起支持 DOM2 方法。
//<div id="div">click this division</div>
var div = document.getElementById('div');
function sayHi() {
console.log('hi');
}
function sayHi2() {
console.log('hi again');
}
div.addEventListener('click', sayHi, false);
div.addEventListener('click', sayHi2, false);
(4) IE:
绑定:attachEvent()
删除:detachEvent()
参数:事件处理名称(以“on”开头,不同于 DOM)、事件处理函数
注意事项:
[1] 匿名函数同样不能移除;
[2] 跟使用 DOM0 方法事件处理程序的作用域不同。DOM0 方法中事件处理程序在其所属元素的作用域上运行,IE 方法在全局作用域运行,this 等于 window;
[3] 执行顺序跟添加顺序相反;
[4] 老版本 IE 只支持冒泡,所以事件处理程序都会被添加到冒泡阶段;
[5] IE9 开始不建议使用 IE 专属方法,IE11 彻底取消支持。
(5) 在添加事件处理程序事 addEventListener 和 attachEvent 主要有几个区别
1.参数个数不相同,这个最直观,addEventListener 有三个参数,attachEvent 只有两个,attachEvent 添加的事件处理程序只能发生在冒泡阶段,addEventListener 第三个参数可以决定添加的事件处理程序是在捕获阶段还是冒泡阶段处理(我们一般为了浏览器兼容性都设置为冒泡阶段)
2.第一个参数意义不同,addEventListener 第一个参数是事件类型(比如 click,load),而 attachEvent 第一个参数指明的是事件处理函数名称(onclick,onload)
3.事件处理程序的作用域不相同,addEventListener 的作用域是元素本身,this 是指的触发元素,而 attachEvent 事件处理程序会在全局变量内运行,this 是 window,所以刚才例子才会返回 undefined,而不是元素 id
4.为一个事件添加多个事件处理程序时,执行顺序不同,addEventListener 添加会按照添加顺序执行,而 attachEvent 添加多个事件处理程序时顺序无规律(添加的方法少的时候大多是按添加顺序的反顺序执行的,但是添加的多了就无规律了),所以添加多个的时候,不依赖执行顺序的还好,若是依赖于函数执行顺序,最好自己处理,不要指望浏览器
(6) 跨浏览器:
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
} else if (node.attachEvent) {
node.attachEvent('on' + type, function () {
handler.apply(node);
});
return true;
}
return false;
}
这样处理就可以解决 handler 中有 this 的问题了,但是新的问题又来了,我们这样等于添加了一个匿名的事件处理程序,无法用 detachEvent 取消事件处理程序,有很多解决方案,我们可以借鉴大师的处理方式,jQuery 创始人 John Resig 很巧妙地利用了闭包,是这样做的
function addEvent(node, type, handler) {
if (!node) return false;
if (node.addEventListener) {
node.addEventListener(type, handler, false);
return true;
} else if (node.attachEvent) {
node['e' + type + handler] = handler;
node[type + handler] = function () {
node['e' + type + handler](window.event);
};
node.attachEvent('on' + type, node[type + handler]);
return true;
}
return false;
}
在取消事件处理程序的时候
function removeEvent(node, type, handler) {
if (!node) return false;
if (node.removeEventListener) {
node.removeEventListener(type, handler, false);
return true;
} else if (node.detachEvent) {
node.detachEvent('on' + type, node[type + handler]);
node[type + handler] = null;
}
return false;
}
事件委托(事件代理)是什么?
事件委托的意义: 1.委托事件有一个优势,他们能在后代元素添加到文档后,可以处理这些事件。在确保所选择的元素已经存在的情况下,进行事件绑定时,可以使用委派的事件,以避免频繁的绑定事件及解除绑定事件。 2.除了可以给未创建的后代元素绑定事件外,代理事件的另一个好处就是,当需要监视很多元素的时候,代理事件的开销更小。
原理:
事件委托利用了事件冒泡,只绑定一个事件处理程序,就可以管理某一类型的所有事件。
使用事件委托只需要在 DOM 树中尽量高的层次上添加一个使用了 switch-case 的事件处理程序。
适用情况:
所有用到按钮的事件都适合采用事件委托,最适合的有 click、mousedown、mouseup、keydown、keyup、keypress。
移除事件处理程序:
内存中留有过时不用的空事件处理程序会造成性能问题: 1.文档中移除带有事件处理程序的元素(removeChild()、replaceChild()、innerHTML) 2.卸载页面
针对 1:如果知道某个元素即将被移除,最好手工移除事件处理程序,如 bt.onclick = null;
针对 2:在页面卸载前通过 onunload 事件处理程序移除所有事件处理程序。这时使用事件委托会有明显优势,即需要跟踪的事件处理程序越少,移除越容易。
其他
1.stopImmediatePropagation 的使用
这是 w3c 的东西,使用的也不是特别多,我们知道 stopPropagation 可以阻止事件的进一步传播,但是他阻止不了该元素上绑定的其他函数的执行,比如我们在 obj 上绑定了 func1 和 func2,如果我们在 func1 中使用了 stopPropagation,那 func2 依然还是会执行出来。倘若这里使用 stopImmediatePropagation,结果就不一样了,他不仅阻止事件的传播,还阻止 func2 的执行。