DOM0事件模型(原始事件模型)
有两种实现方式
- 通过元素属性来绑定事件
<body>
<script>
// 通过元素属性来绑定事件
function myFuntion() {
console.log("触发点击了")
}
</script>
<button onclick="myFuntion()">点我</button>
</body>
2.先获取页面元素,然后以赋值的形式来绑定事件
<body>
<button id="myClick">点我</button>
<script>
// 先获取页面元素,然后以赋值的形式来绑定事件
const btn = document.getElementById("myClick")
btn.onclick = function () {
console.log("触发点击了")
}
btn.onclick = null; // 解除事件
</script>
</body>
一个dom节点只能绑定一个事件,再次绑定将会覆盖之前的事件。
DOM0级事件模型是早期的事件模型,所有的浏览器都是支持的
DOM2事件模型
dom2新增冒泡和捕获的概念,并且支持一个元素节点绑定多个事件
- 事件捕获和事件冒泡(capture,bubble )
DOM2 级事件模型共有三个阶段:
- 事件捕获阶段:事件从 document 向下传播到目标元素,依次检查所有节点是否绑定了监听事件,如果有则执行。
- 事件处理阶段:事件在达到目标元素时,触发监听事件。
- 事件冒泡阶段:事件从目标元素冒泡到 document,并且一次检查各个节点是否绑定了监听函数,如果有则执行。
注册事件和解除事件
在DOM2级中使用addEventListener和removeEventListener来注册和解除事件(IE8及之前版本不支持)。这种函数较之之前的方法好处是一个dom对象可以注册多个相同类型的事件,不会发生事件的覆盖,会依次地执行各个事件函数。
addEventListener有三个参数 事件名称、事件回调、捕获/冒泡
<body>
<button id="myClick">点我</button>
<script>
var btn = document.getElementById("myClick")
btn.addEventListener("click", function() {
console.log("first")
}, true)
btn.addEventListener("click", function() {
console.log("second")
}, true)
</script>
</body>
设置为true,则事件在捕获阶段执行,为false则在冒泡阶段执行。
下面演示如何使用事件流的发生机制
<body>
<div id="outer">
<button id="myClick">点我</button>
</div>
<script>
var outer = document.getElementById("outer")
var btn = document.getElementById("myClick")
outer.addEventListener("click", function() {
console.log("outer")
}, true)
btn.addEventListener("click", function() {
console.log("inner")
}, true)
</script>
</body>
这段代码,我们使用了捕获事件,由于inner是嵌套在outer中的,那么结果就是outer首先执行,其次是inner执行。
那么我把outer的执行时机改为冒泡的阶段呢?
outer.addEventListener("click", function() {
console.log("outer")
}, false) // outer的执行时机改为冒泡的阶段
这种情况下,就是先执行inner后执行outer了
同理我们把二者的事件执行时机都改为冒泡阶段的话,依旧是先执行inner后执行outer。
那么还有个问题,就是如果我们把inner注册两个click事件,一个是在捕获阶段,另一个是在冒泡阶段,也就是说把addEventListenter的第三个参数分别设置为false和true,那么执行的顺序又是怎样的呢。
<body>
<div id="outer">
<button id="myClick">点我</button>
</div>
<script>
var btn = document.getElementById("myClick")
btn.addEventListener("click", function() {
console.log("inner 捕获")
}, false)
btn.addEventListener("click", function() {
console.log("inner 冒泡")
}, true)
</script>
</body>
但是这种结果是与注册的顺序有关系的,先注册就先执行。发现最后具体的dom对象是只有一个的。
如果我们给outer和inner都注册了click事件但是我不希望outer执行怎么办呢?这个时候我们就需要用到stopPropagation函数了,这个函数是用来阻止冒泡
<body>
<div id="outer">
<button id="myClick">点我</button>
</div>
<script>
var outer = document.getElementById("outer")
var btn = document.getElementById("myClick")
outer.addEventListener("click", function() {
console.log("outer")
}, false)
btn.addEventListener("click", function(event) {
console.log("inner")
event.stopPropagation(); // 阻止事件冒泡
}, false)
// 这段代码,我们在inner阻止了冒泡事件, 不会触发outer的点击事件
</script>
</body>
由于事件捕获阶段没有可以阻止事件的函数,所以一般都是设置为事件冒泡。
IE事件模型
IE事件只支持冒泡,所以事件流有两个阶段:
- 事件处理阶段:事件在达到目标元素时,触发监听事件。
- 事件冒泡阶段:事件从目标元素冒泡到 document,并且一次检查各个节点是否绑定了监听函数,如果有则执行。
<body>
<div id="outer">
<button id="myClick">点我</button>
</div>
<script>
// 该事件模型只在IE中有效
var btn = document.getElementById("myClick")
function handler() {
console.log("inner")
}
btn.attachEvent("onclick", handler) // 绑定事件
// btn.detachEvent("onclick", handler) // 移除事件
</script>
</body>
事件冒泡的使用场景
原理:利用冒泡的原理,把事件添加到父元素上,委托它们父级代为执行事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS 事件委托</title>
<style>
*{
margin: 0;
padding: 0;
}
ul{
list-style: none;
width: 400px;
border: 1px solid #000;
margin: 100px auto;
}
li{
width: 100%;
height: 50px;
border-bottom: 1px solid #000;
box-sizing: border-box;
}
.selected{
background-color: red;
}
</style>
</head>
<body>
<ul>
<li class="selected">1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<script>
let uItems = document.querySelectorAll('li');
let currentItem = uItems[0];
for (let item of uItems){
item.onclick = changeBg;
}
function changeBg() {
currentItem.className = '';
this.className = 'selected';
currentItem = this;
}
</script>
</body>
</html>
这是一般做法,缺点是保存了多份onclick的函数
事件委托的做法:监听ul的点击,而不是直接监听li的点击,li将事件冒泡到它的父元素ul,再通过事件对象拿到当前被点击的元素
<script>
let oUl = document.querySelector('ul');
let oLi = document.querySelector('.selected');
oUl.onclick = function (event) {
event = event || window.event; // 兼容所有浏览器
oLi.className = '';
let item = event.target; // event.target为当前点击的li
item.className = 'selected';
oLi = item;
}
</script>
事件委托的好处:
- 减少事件数量,提高性能,减少内存
- 预测未来元素,新添加的元素仍然可以触发该事件
- 避免内存外泄,在低版本的IE中,防止删除元素而没有移除事件而造成的内存溢出
事件委托的坏处:
- 事件委托基于冒泡,不冒泡的事件不支持
- 层级过多,冒泡过程中可能被中间层阻止
- 如果把所有事件都用事件委托,可能会出现事件误判,即不该触发事件的被绑定了
项目地址:https://gitee.com/smallgrey/js