事件流
事件流描述的是从页面中接收事件的顺序。在浏览器开发过程中,提出了两种差不多相反的事件流概念,即事件冒泡和事件捕获。
事件冒泡
事件开始时由最具体的元素接收,然后逐次向上传播,直到到达最顶层。(老版浏览器是冒泡到 document 对象,而目前普遍的浏览器都是冒泡到 window 对象)
事件捕获
最顶层的节点应该更早接收到事件,而最具体的节点应该最后接收到事件,用意在于在事件到达目标节点前捕获它。(虽然“DOM2级事件”规范要求事件应该从 document 对象开始传播,但是目前的主流浏览器都是从 window 对象开始传播的)
"DOM2级事件"规定事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段
事件处理程序
事件就是用户或者浏览器自身执行的某种动作。而响应事件的函数就叫做函数处理程序。
HTML事件处理程序
<input type="button" value="Click me" onClick="showMessage()" />
<script type="text/javascript">
function showMessage(event) {
console.log(event.type) // 输出事件类型:click,通过event变量可以直接访问事件对象
console.log(this.value) // 输出:Click me,函数内部this等于事件的目标元素
alert('Clicked!')
}
</script>
DOM0级事件处理程序
DOM0级事件模型是早期的事件模型,所有的浏览器都是支持的,而且其实现也是比较简单。代码如下:
<p id='click'>click me</p>
<script>
document.getElementById('click').onclick = function(event){
alert(event.target);
}
</script>
这种事件模型就是直接在 DOM 对象上注册事件名称,上述代码是在p标签上注册了一个 onclick 事件,在这个事件函数内部输出点击的目标,该事件会在事件流的冒泡阶段被处理。而解除事件则更加简单,就是将 null 复制给事件函数,如下:
document.getElementById('click'_).onclick = null;
在DOM0级事件里,一个 DOM 对象只能注册一个同类型的函数,因为注册多个同类型的函数的话,就会发生覆盖,之前注册的函数就会无效。
var click = document.getElementById('click');
click.onclick = function(){
alert('you click the first function'); // 不会执行,被后面注册的函数所覆盖
};
click.onclick = function(){
alert('you click the second function') // 执行
}
DOM2级事件处理程序
"DOM2级事件"中使用 addEventListener 和 removeEventListener 方法来注册和删除事件(IE8及之前版本不支持)。这种方式较之之前的方法好处是 同一个DOM对象可以注册多个相同类型的事件,不会发生事件的覆盖,会依次的执行各个事件函数(无论事件类型是冒泡还是捕获,都会安装代码顺序依次执行)。
addEventListener('事件名称','事件回调','捕获/冒泡')
removeEventListener('事件名称','事件回调','捕获/冒泡')
// 接收三个参数:要处理的事件名、事件处理函数、一个布尔值
// 最后的Boolean型参数,如果是true,表示在事件捕获阶段调用事件处理程序;如果是false,表示在事件冒泡阶段调用事件处理程序
var btn = document.getElementById("myBtn")
btn.addEventListener('click',function(){
console.log(this.id)
}, false)
btn.addEventListener('click',function(){
console.log('Hello!')
}, false)
// 输出结果依次:myBtn, Hello!
// 上述代码为按钮添加了两个事件处理程序,这两个事件处理程序会按照它们的添加顺序来触发
var btn = document.getElementById("myBtn")
btn.addEventListener('click',function(){
console.log('add click event')
}, false)
btn.removeEventListener('click',function(){ // 无效!!!
console.log('remove click event')
}, false)
// 通过 addEventListener 添加的事件处理程序只能通过 removeEventListener 来移除
// 移除时传入的参数和添加处理处理程序时所使用的参数必须相同
// 所以通过 addEventListener 添加的匿名函数将无法移除
var click = document.getElementById('inner')
var clickouter = document.getElementById('outer')
click.addEventListener('click',function(event) {
alert('inner show')
event.stopPropagation() // 阻止事件进一步的捕获或冒泡,条件是event对象的bubbles属性为true
event.preventDefault() // 取消事件的默认行为,条件是event对象的cancelable属性为true
},false)
clickouter.addEventListener('click',function() {
alert('outer show') // 不会执行
},false)
IE事件处理程序
在IE中则是使用 attachEvent 和 detachEvent 方法来实现事件的注册和删除。接收两个参数:事件名称、事件处理函数。
var btn = document.getElementById("myBtn")
btn.attachEvent('onclick',function(){ // 注意第一个参数是 “onclick” 而不是 “click”
console.log('add click event')
console.log(this === window) // true, 使用attachEvent方法,事件处理程序会在全局作用域运行
}, false)
通过 attachEvent 添加的事件可以通过 detachEvent 来移除,条件是必须提供相同的参数。所以通过 attachEvent 添加的匿名函数将无法移除。
var btn = document.getElementById("myBtn")
btn.attachEvent('click',function(){
console.log('Clicked')
}, false)
btn.attachEvent('click',function(){
console.log('Hello!')
}, false)
// 输出结果依次:Hello!, Clicked
// 上述代码调用了两次 attachEvent 方法添加了两个不同的事件处理程序
// 不过这些事件处理程序不是以它们的添加程序执行,而是以相反的顺序被触发。
跨浏览器的事件处理程序和事件对象
var EventUtil = {
addHandler: function (element, eventType, handler) {
if(element.addEventListener) {
element.addEventListener(eventType, handler, false);
} else if(element.attachEvent) {
element.attachEvent("on"+eventType, handler);
} else {
element["on"+eventType] = handler;
}
},
removeHandler: function (element, eventType, handler) {
if(element.removeEventListener) {
element.removeEventListener(eventType, handler, false);
} else if(element.detachEvent) {
element.detachEvent("on"+eventType, handler);
} else {
element["on"+eventType] = 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.cancelBubble = true
}
},
}
// 使用
var btn = document.getElementById("myBtn")
var handler = function (event) {
console.log('Clicked')
event = EventUtil.getEvent(event)
EventUtil.preventDefault()
EventUtil.stopPropagation()
}
EventUtil.addHandler(btn, 'click', handler)
EventUtil.removeHandler(btn, 'click', handler)