1. 事件
当用户浏览网页时,存在许许多多与网页交互的操作。例如按钮的点击、屏幕的滑动、鼠标的移动等等,通过这些交互完成某些操作,达到某种效果。我们可以将这些交互称之为事件。
2. 事件冒泡
事件冒泡是指事件在某个元素上触发后一直向上传播(父元素),一直传播到document。如果向上冒泡的过程中遇到相同的事件,则触发。例如下面的例子:
<body>
<div id="myDiv">
<ul>
<li id="myLi">1</li>
</ul>
</div>
<script>
const li = document.getElementById('myLi')
li.onclick = function () {
console.log(this.id); //myLi
}
const div = document.getElementById('myDiv')
div.addEventListener('click', function () {
console.log(this.id); //myDiv
})
</script>
</body>
当我们点击li标签的时候,会触发li上绑定的click事件,但是在触发li的click事件后会触发div上绑定的click事件。这就是事件冒泡,其实仔细想想这也是可以理解的,因为点击了li标签则意味着div也被点击了。就像你住在中国的某个省,那你肯定住在中国。上面的冒泡总过程如下:
li 》ul 》 div 》body 》html 》document
3. 事件捕获
事件捕获与事件冒泡正好是个相反的过程,事件捕获会从document开始,然后一次向下传播,直到具体的元素
//dom2事件处理程序才能控制事件捕获与事件冒泡
//并且只有addEventListener的第三个参数设置为true才会是事件捕获,false是事件冒泡。
const lis = document.getElementsByTagName('li')
for (const li of lis) {
li.addEventListener('click', () => {
console.log('li click');
}, true)
}
const div = document.getElementById('myDiv')
div.addEventListener('click', () => {
console.log('div click');
}, true)
可以看出事件捕获与事件冒泡确实是相反的,事件捕获先执行了div的click事件,然后执行li的click事件。捕获的顺序如下
document 》html 》body 》div 》ul 》li
4. 事件模型
4.1 DOM0事件模型
DOM0事件模型比较简单,只需要将事件处理函数赋值给一个事件处理程序属性。目前的所有浏览器都支持该方式。
const div = document.getElementById('myDiv')
div.onclick = function () {
console.log(this.id) // myDiv
}
DOM0事件模型中移除事件方式直接将事件处理程序属性的值设置为null就行
div.onclick = null
4.2 DOM2事件模型
DOM2事件模型为事件处理程序的赋值和移除定义了两个方法:addEventListener()和removeEventListener()。它们接收3个参数:事件名、事件处理函数、布尔值(默认false),true表示捕获阶段调用事件处理程序,false表示冒泡阶段调用事件处理程序。
const div = document.getElementById('myDiv')
const handler = function () {
console.log(this.id);
}
// 绑定click事件
div.addEventListener('click', handler)
// 移除click事件
div.removeEventListener('click', handler)
// 绑定click事件
div.addEventListener('click', function () {
console.log(this.id);
})
// 无法移除click事件
div.removeEventListener('click', function () {
console.log(this.id);
})
tip: 传给addEventListener、removeEventListener的事件处理函数必须先赋值给一个变量,然后共同使用才能对绑定的事件进行移除,否则移除不了绑定的事件。因为在无法移除的情况下,虽然事件处理函数看起来是相同的,但是实际上是两个不同的函数,有不同的内存地址。
4.3 IE事件模型
IE事件模型为事件处理程序的赋值和移除定义了两个方法:attachEvent()和attachEvent()。它们接收2个参数:事件名、事件处理函数。因为 IE8 及更早版本只支持事件冒泡,所以使用 attachEvent()添加的事件处理程序会添加到冒泡阶段。
const div = document.getElementById('myDiv')
const handler = function () {
console.log(this.id);
}
//添加点击事件,注意是onClick
div.attachEvent("onclick", handler);
//移除事件
div.detachEvent("onclick", handler);
4.4 3种事件模型的其他事项
DOM0事件中this指向当前元素
DOM0事件中为同一个元素赋值两次onclick,后面会覆盖前面的(因为是赋值)
DOM2事件中this指向当前元素
DOM2可以通过addEventListener为同一个元素添加多个事件函数,并且顺序执行(观察者模式)
IE事件模型中事件处理程序是在全局作用域中运行的,因此 this 等于 window
IE事件模型可以通过attachEvent为同一个元素添加多个事件函数,但是是逆序执行的,即先添加的最后执行
5. 事件委托
事件委托又称为事件代理,事件委托就是将原本绑定到子元素的事件委托给父元素。在开发中最常见的就是菜单栏由ul和多个子li标签组成。如果要为菜单栏添加点击事件,通常方法是直接在li上绑定对应的点击事件。但是这样不利于未来添加其他的菜单项,即所谓的可扩展性。而且为每个li添加事件是费时费力的。所以通常解决办法是利用事件冒泡的特性,将事件绑定到ul标签上。
通俗的例子就是寝室中,本来是每个人都要去取快递的,但是一想这样太浪费人力了,所以非常民主地选择寝室长一个人去,一起代领。
下面看下实际的例子:
<body>
<ul id="myUl">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
//事件委托实现
const ul = document.getElementById('myUl')
ul.onclick = function (e) {
console.log(e.target.innerHTML);
}
//事件委托的等效代码:为每个li添加事件,
const lis = document.getElementsByTagName('li')
for (const li of lis) {
li.onclick = function () {
console.log(this.innerHTML);
}
}
</script>
</body>
事件委托可以通过从事件对象的target属性中获取点击的目标,可以看出使用事件委托,只需要在父元素上绑定所需的事件。而通过一般的事件绑定则需要为每个子元素绑定事件。
事件委托的优势:
通过使用事件委托不需要为每个子元素设置事件函数,可以减少页面所需的内存,提升页面的整体性能;实现事件的动态绑定,新增节点不必单独添加监听事件,发生的事件交给父元素。