JavaScript 的事件模型

事件流

事件流描述的是从页面中接收事件的顺序。在浏览器开发过程中,提出了两种差不多相反的事件流概念,即事件冒泡和事件捕获。

事件冒泡

事件开始时由最具体的元素接收,然后逐次向上传播,直到到达最顶层。(老版浏览器是冒泡到 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级事件"中使用 addEventListenerremoveEventListener 方法来注册和删除事件(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中则是使用 attachEventdetachEvent 方法来实现事件的注册和删除。接收两个参数:事件名称、事件处理函数。

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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值