事件监听浏览器和用户的动作并执行相对应的事件处理程序,实现了两者的动态异步交互。事件处理程序则是交互瞬间的执行函数。这种在传统软件工程中称之为观察者模式。
一、事件流
1、事件冒泡
事件冒泡就是从绑定事件的元素开始,依次向上层传递事件,直至传递到document对象结束
2、事件捕获
事件捕获就是从最外层开始触发事件,直至传递到所绑定事件的元素结束
3、DOM事件流
首先得说明一点,DOM事件流整合了上述两种基本事件流方式。在DOM2事件流规定事件包括三个阶段:事件捕获阶段、处于目标状态阶段和事件冒泡阶段。
事件捕获阶段:事件由最外层开始捕获传入到目标节点的父节点事件传递完毕过程
处于目标阶段:事件由开始传入目标节点到目标节点事件传递完毕过程
事件冒泡阶段:事件由开始冒泡传入目标节点父节点到最外层节点事件传递完毕过程
且DOM2事件流规定事件不可在捕获阶段进行事件处理程序的执行,但是各大浏览器并没有遵守这个规定。在对应的addEventListener(eventType, handler, true/false)
中,第三个参数代表着在那个阶段执行事件处理程序,默认为false
冒泡阶段。
二、事件处理程序
1、HTML事件处理程序
1.1、事件处理程序绑定方式
内联绑定
<button onclick="console.log(this)">点我</button>
外联绑定
<button onclick="onClick()">点我</button>
<script>
function onClick() {
console.log(this)
}
</script>
1.2、处理程序内部this
指向
内联绑定:代码块(不是函数)中this
指向所被绑定元素
外联绑定:事件处理程序函数中this
指向全局Window
1.3、异常收集
内联绑定:不需要
外联绑定:将外部的函数放入到try{}catch(e){}
中
<button onclick="try{ onClick() }catch(e) { console.log('异常') }">点我</button>
<script src="./test.js"></script>
// test.js
function onClick() {
console.log(this)
}
防止当用户点击时,test.js
还未加载完成,从而产生的报错。
2、DOM0级事件处理程序(通过js绑定事件处理函数)
2.1、绑定方法
<button>点我</button>
<script>
const buttonNode = document.querySelector('button')
buttonNode.onclick = function() {
console.log(this)
}
</script>
2.2、内部this
指向
事件处理程序函数中this
指向所被绑定元素
2.3、编写形式注意事项
事件处理程序函数不可使用箭头函数编写,不然其内部this
指向为全局Window
,这是因为箭头函数没有this
,其内部this指向是其父环境this
指向。因此强烈不建议事件处理程序函数使用箭头函数。
2.4、内存释放
当确定某个函数处理程序函数执行完毕后,之后就不会执行,我们就有必要将其接触事件名引用,释放事件处理程序对象内存。
<button>点我</button>
<script>
const buttonNode = document.querySelector('button')
buttonNode.onclick = function() {
// delayCodes……
buttonNode.onclick = null // 引用解除(可了解jc中GC机制)
}
</script>
3、DOM2级事件处理程序
3.1、绑定方法
<button>点我</button>
<script>
function click(params) {
console.log(this)
}
const buttonNode = document.querySelector('button')
buttonNode.addEventListener('click', click, false)
</script>
false
表示冒泡阶段函数执行,true
表示捕获阶段函数执行,默认为false
3.2、内部this
指向
事件处理程序函数中this
指向所被绑定元素
3.3、解除事件处理程序
buttonNode.removeEventListener('click', click, false)
3.4、注意事项
当添加绑定事件处理程序函数时,此函数为一个匿名函数,则无法被解除释放,因为使用addEVentListener()
接口绑定的事件处理程序只能由removeEventListener()
接触,但是匿名函数没有函数名,因此removeEventListener()
无法获得函数名,就此也无法解除其这个函数所代表的事件处理程序。若事件处理函数程序未被解除,那么就算是清除所绑定的DOM节点,但是其所附带的事件处理程序在内存中还是存在,未被清除。
4、IE事件处理程序
在这里我只说说它们独有的两种方法
attachEvent('onclick', handler)
detachEvent('onclick', handler)
不想多讲,只觉得IE很。。。。不过可以用作为浏览器能力检测(是否为万恶的XX),这倒是不错。
5、自定义跨浏览器事件处理程序
所谓跨浏览器事件处理程序就是考虑多种浏览器下的事件处理程序的绑定,使用能力检测手段对浏览器进行能力检测,从而使用相对应得方法绑定事件处理程序。在此我就写下简易版。
class EventUtil {
constructor(target, eventType, handler, relevantTagle) {
this.target = target
this.eventType = eventType
this.handler = handler
this.relevantTagle = relevantTagle
}
addHandler() {
const self = this
if(self.target.addEventListener) {
self.target.addEventListener(self.eventType, self.handler, self.relevantTagle)
return ''
}
if(self.target.attachEvent) {
self.target.attachEvent(`on${self.eventType}`, self.handler)
return ''
}
self.target[`on${self.eventType}`] = self.handler
}
removeHandler() {
const self = this
if(self.target.removeEventListener) {
self.target.removeEventListener(self.eventType, self.handler, self.relevantTagle)
return ''
}
if(self.target.detachEvent) {
self.target.detachEvent(`on${self.eventType}`, self.handler)
return ''
}
self.target[`on${self.eventType}`] = null
}
}
// 另外一种思想:利用try...catch...来检测浏览器能力并且赋予捕捉异常能力
三、事件对象
1、DOM中的事件对象
获取方式:event
在触发DOM上的某个事件时,会产生一个事件对象,这个对象其内部包含着所有与事件相关联的信息。包括触发事件的元素,绑定事件的元素,事件的类型等特定的相关信息。当事件执行程序执行完毕后,事件对象便会备销毁。
不同的事件类型,其事件对象内部某些信息也有所不同,但是所有事件都会供有某些属性和方法。
具体就不在此细说:详见MDN
2、IE中的事件对象
获取方式:window.event
与DOM的事件获取方式不同的是,IE是将event对象作为window属性而存在的。并且存在某些事件对象中的信息表示方法也与DOM有些许出入,但是所有IE事件对象都共有某些属性。
cancelBubble,returnValue、srcElement、type
3、跨浏览器的事件对象
function name(event) {
const event = event || window.event
}
四、内存和性能
为了更加完善Web交互,就必需添加更多的事件处理程序。但是若为每个相应的DOM节点添加事件处理程序,又极大的浪费内存,是网页性能变得缓慢。因此,我们可以利用事件的冒泡机制在父节点或祖节点绑定定事件处理程序,从而代理了子节点的事件处理程序,减少了对子节点事件处理程序的添加,降低了内存占用。
1、事件代理委托
// 主入口
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="wrap">
<button>点我</button>
<input type="text" placeholder="input/change我">
</div>
<script src="./eventUtil.js"></script>
<script>
const wrapNode = document.getElementById('wrap')
const handler = function (event) {
const even = event || window.event
switch (even.type) {
case 'click':
console.log('点击事件')
break
case 'input':
console.log('输入事件')
break
case 'change':
console.log('改变事件')
break
}
}
const clickEventUtil = new EventUtil(wrapNode, 'click', handler)
const inputEventUtil = new EventUtil(wrapNode, 'input', handler)
const changeEventUtil = new EventUtil(wrapNode, 'change', handler)
clickEventUtil.addHandler()
inputEventUtil.addHandler()
changeEventUtil.addHandler()
</script>
</body>
</html>
// eventUtil.js
'use strict'
class EventUtil {
constructor(target, eventType, handler, relevantTagle = false) {
this.target = target
this.eventType = eventType
this.handler = handler
this.relevantTagle = relevantTagle
}
addHandler() {
const self = this
if (self.target.addEventListener) {
self.target.addEventListener(self.eventType, self.handler, self.relevantTagle)
return ''
}
if (self.target.attachEvent) {
self.target.attachEvent(`on${self.eventType}`, self.handler)
return ''
}
self.target[`on${self.eventType}`] = self.handler
}
removeHandler() {
const self = this
if (self.target.removeEventListener) {
self.target.removeEventListener(self.eventType, self.handler, self.relevantTagle)
return ''
}
if (self.target.detachEvent) {
self.target.detachEvent(`on${self.eventType}`, self.handler)
return ''
}
self.target[`on${self.eventType}`] = null
}
}
2、移除事件处理程序
removeHandler() {
const self = this
if (self.target.removeEventListener) {
self.target.removeEventListener(self.eventType, self.handler, self.relevantTagle)
return ''
}
if (self.target.detachEvent) {
self.target.detachEvent(`on${self.eventType}`, self.handler)
return ''
}
self.target[`on${self.eventType}`] = null
}