Javascript与HTML之间的交互是通过事件来实现的。事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用侦听器来预定事件,以便事件发生时执行相应的代码。
事件流
事件流 是指从页面接收事件的顺序。有意思的是,IE和网景提出不同的事件流概念。IE支持的事件流是事件冒泡,网景公司支持的事件流是事件捕获。
事件冒泡
IE的事件流叫做事件冒泡,即事件由内向外找监听函数。我们来看下面的代码:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="div1">click Me</div>
</body>
</html>
如果单击了页面的div元素,click事件会按照以下顺序传播:
- <div>
- <body>
- <html>
- <body>
- document
传播的过程是从div到document传播,逐级向上传递。
事件捕获
网景团队的事件流叫事件捕获 ,即事件由外向内找监听函数。以前面的代码为例,监听的顺序是从document向<div>传播。顺序如下:
- document
- <html>
- <body>
- <div>
DOM事件流
"DOM2级事件"规定的事件流包括三个阶段: 事件捕获阶段,处于目标阶段、事件冒泡阶段 。首先发生的是事件捕获,为截取事件提供机会;然后是实际的目标接收到事件,最后是冒泡阶段。
addEventLister
事件绑定的Api如下:
- IE5*:
baba.attachEvent('onClick',fn)
//冒泡 - 网景:
baba.addEventListener('onClick',fn)
//捕获 - w3c:
baba.addEventListener('onClick',fn,bool)
其中,bool不传或者为falsy值,fn会走冒泡;如果bool为true,fn会走捕获阶段。 接下来,我们来做个事件先捕获再冒泡的小练习吧!
html代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div class="level1 x">
<div class="level2 x">
<div class="level3 x">
<div class="level4 x">
<div class="level5 x">
<div class="level6 x">
<div class="level7 x"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
css代码:
*{
box-sizing:border-box;
}
div[class^=level]{
border:1px solid;
border-radius:50%;
display:inline-flex;
}
.level1{
padding:10px;
background:purple;
}
.level2{
padding:10px;
background:blue;
}
.level3{
padding:10px;
background:cyan;
}
.level4{
padding:10px;
background:green;
}
.level5{
padding:10px;
background:yellow;
}
.level6{
padding:10px;
background:orange;
}
.level7{
width:50px;
height:50px;
border:1px solid;
background:red;
border-radius:50%;
}
.x{
background:transparent;
}
js代码:
const level1=document.querySelector('.level1')
const level2=document.querySelector('.level2')
const level3=document.querySelector('.level3')
const level4=document.querySelector('.level4')
const level5=document.querySelector('.level5')
const level6=document.querySelector('.level6')
const level7=document.querySelector('.level7')
let n=1;
let fn=((e)=>{
const t=e.currentTarget
setTimeout(()=>{
t.classList.remove('x')
},n*1000)
n+=1
})
let fn2=((e)=>{
const t=e.currentTarget
setTimeout(()=>{
t.classList.add('x')
},n*1000)
n+=1
})
level1.addEventListener('click',fn,true)
level1.addEventListener('click',fn2)
level2.addEventListener('click',fn,true)
level2.addEventListener('click',fn2)
level3.addEventListener('click',fn,true)
level3.addEventListener('click',fn2)
level4.addEventListener('click',fn,true)
level4.addEventListener('click',fn2)
level5.addEventListener('click',fn,true)
level5.addEventListener('click',fn2)
level6.addEventListener('click',fn,true)
level6.addEventListener('click',fn2)
level7.addEventListener('click',fn,true)
level7.addEventListener('click',fn2)
最后程现的效果如下:
上述实验从而证明了w3c的事件模型:事件先捕获再冒泡。需要注意的是:e对象被传给所有监听函数,事件结束后,e对象就不存在了。
取消冒泡
需要注意的是:冒泡可以取消,而捕获取消不了。用e.stopPropagation()
可以中断冒泡。
代码沿用上例,js代码改为:
level1.addEventListener('click',remove)
level2.addEventListener('click',remove)
level3.addEventListener('click',(e)=>{
e.stopPropagation()
})
level4.addEventListener('click',remove)
level5.addEventListener('click',remove)
level6.addEventListener('click',remove)
level7.addEventListener('click',remove)
到level3就停止冒泡了。
target和currentTarget
e.target
用户操作的元素e.currentTarget
开发者监听的元素
事件代理
事件代理 即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务
,事件代理的原理是DOM元素的事件冒泡,使用事件代理的好处是可以提高性能。
事件代理的使用场景如下:
场景一: 要给100个按钮添加点击事件,怎么办?
答: 监听这100个按钮的祖先,等冒泡的时候判断target是不是这100个按钮中的一个。
代码实现:
html代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="div1">
<span>span1</span>
<button>click1</button>
<button>click2</button>
<button>click3</button>
<button>click4</button>
<button>click5</button>
<button>click6</button>
<button>click7</button>
/*中间为click8-click99*/
<button>click100</button>
</div>
</body>
</html>
js代码:
div1.addEventListener('click',(e)=>{
const t=e.target
if(t.tagName.toLowerCase()==='button'){
console.log('botton被点击了')
console.log('button的Id是'+t.textContent)
}
})
使用场景二:要监听目前不存在的元素的点击事件,怎么办?
答:监听祖先,等点击的时候看看是不是想要监听的元素。
html代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div id="div1"></div>
</body>
</html>
js代码:
setTimeout(()=>{
const button=document.createElement('button')
button.textContent='click 1'
div1.appendChild(button)
},1000)
div1.addEventListener('click',(e)=>{
const t=e.target
if(t.tagName.toLowerCase()==='button'){
console.log('button 被click')
}
})