在JavaScript中,事件模型是非常重要的一个知识点,在这里我将对事件模型做一个浅薄的分析。了解事件模型之前先让我们来了解一下什么是事件,事件(Event)是JavaScript应用跳动的心脏 ,也是把所有东西粘在一起的胶水。当我们与浏览器中 Web 页面进行某些类型的交互时,事件就发生了,它是JS与HTML之间交互的桥梁。
DOM介绍
DOM
DOM全拼为Document Object Model(文档对象模型)是一种用于HTML和XML文档的编程接口,它给文档提供了一种结构化的表示方法,可以改变文档的内容和呈现方式。DOM树
DOM实际上是以面向对象方式描述的文档模型。DOM定义了表示和修改文档所需的对象、这些对象的行为和属性以及这些对象之间的关系。可以把DOM认为是页面上数据和结构的一个树形表示如图1所示,不过页面当然可能并不是以这种树的方式具体实现(html -> brower(解析成一棵 DOM树)),js操作树就叫dom操作。
图1
举个例子:
<body>
<div class="wrpper">
<!-- js插入一些节点 -->
</div>
<script>
// dom操作
// 代码都是字符串(一定的格式)
// html -> brower(解析成一棵 DOM树)->js操作我们这棵树就叫dom操作
// DOM 标准规定了 这些 api
const wrpper = document.querySelector('.wrpper');
wrpper.innerHTML = `
<li>1</li>
<li>2</li>
<li>3</li> `
</script>结果:
js操作中插入的li标签会解析成一棵DOM树上的根节点
事件模型的三个阶段
在浏览器发展的过程中,对于事件模型阶段的定义都有不同的标准,如IE:冒泡 bubble,firefox:捕获 capture,最终 w3c制定 统一的规范:capture,bubble 融合(如图一所示),在这里我将做一个浅薄的分析,以鼠标点击事件为例。
冒泡阶段
举个例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.parent {
width: 300px;
height: 300px;
border: 1px solid #000; }
.child {
width: 100px;
height: 100px;
border: 1px solid red; }
</style>
</head>
<body>
<div class="parent">
parent
<div class="child">
child
</div>
</div>
<script>
const parent = document.querySelector('.parent');
const child = document.querySelector('.child');
parent.addEventListener('click', () => {
console.log('parent 被点击了');
})
child.addEventListener('click', (event) => {
// child 触发 click 的时候 就是 目标阶段
console.log('child 被点击了');
})
</script>
</bodya>如图二所示当我们点击child的时候,会触发一个事件,像个水中的气泡一样一直往上冒,直到顶端。从DOM树型结构上理解,就是事件由叶子节点沿祖先结点一直向上传递直到根节点(如图一Bubble push),这时事件处于冒泡阶段。
捕获阶段
捕获事件:当我们在 DOM 树的某个节点发生了一些操作(鼠标移动上去),就会有一个事件发射过去。这个事件从 Window 发出,不断经过下级节点直到触发的目标节点。在到达目标节点之前的过程,就是捕获阶段(Capture Phase)。
如何将处于冒泡阶段的事件修改成捕获阶段呢?
parent.addEventListener('click', () => {
console.log('parent 被点击了');
},true)
child.addEventListener('click', (event) => {
console.log('child 被点击了');
},true)当处于冒泡阶段时,我们没有必要加第三个参数,因为addEventListener默认第三个参数为false(第三个参数存在),如果我们要事件处于捕获阶段的话,只要将第三个参数设成true就行。
结果:
目标阶段
一般来说,很少人会记得目标阶段,更多的是捕获和冒泡阶段,以例子的代码说明,child 触发 click 的时候 就是 目标阶段。
事件模型的用处
举个例子:
<!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>
<ul>
<li>111</li>
<li>222</li>
<li>34343</li>
<!-- 3s 之后我们在下面插入了 一个 li -->
</ul>
<script>
// 点击每一个li 输出 li里面的 文本内容
// 1:常规 只能选中 当前页面上已有的 li 节点,给他们添加事件,后添加来的 节点 是没有效果的
const lis =document.querySelectorAll('li');
// 每一个li 都绑定事件
lis.forEach((li) => {
li.addEventListener('click',function(){
console.log(li.innerText);
})
})
// 定时器
setTimeout(function(){
// 插入
const ul =document.querySelector('ul');
// 创建一个 li节点:<li></li>
const li =document.createElement('li');
li.innerHTML = '4444';
ul.appendChild(li)
},3000)
</script>
</body>
</html>当我们在页面上定义三个节点时,要求点击每一个li 输出 li里面的 文本内容,这时我们只要将每一个li都绑定事件就行,然后输出就行。
结果:
3s 之后我们在下面插入了 一个 li,再次点击li
结果:
为什么没有出现新插入节点的值?通过分析我们可以知道,当前页面上已有的 li 节点,给他们添加事件,后添加来的 节点 是没有效果的。如果我们要认新出现的节点通过点击节点显示出来,这就要用到js事件模型中的冒泡阶段的知识,借助 冒泡的特点 ,父节点可以监听到子节点有没有发生点击事件(如果子节点点击了,父节点也会收到这个 click事件)。
代码:
const ul =docum .querySelector('ul');
ul.addEventlistener('click',function(event){
const target = event.target;
console.log(target.innerText);
})上述代码中,我们只需要绑定一次事件,通过Event对象的target属性返回事件源(即事件的目标节点),可以做不同的处理,这就是事件代理,原本需要目标元素处理的事件,交由其父元素代为执行。采用事件代理避免了频繁的操作DOM,优化效果可想而知。