做一个小需求的时候,遇到一个 click
事件,眼睛一扫而过也没怎么在意,因为根本没把这东西当一回事,天天用的东西闭着眼睛都不会错,然而这次因为诸多理由居然试了好几次才终于获得想要的效果,这激发了我想要更深一步探究此事件的兴趣。
为一个元素绑定 click
事件,常用的有以下三种方法。
// 为元素绑定事件
element.addEventListener
// 为元素设置事件
element.onclick
// 在元素的标签上显性声明事件
<element onclick="fn()"></element>
三种方法的最终目的都是为元素添加一个 click
事件,但是其中的门道却不太一样。
addEventListener
先上代码:
// HTML
<div class="parent">parent
<div class="child">child</div>
</div>
// CSS
.parent {
width: 200px;
height: 200px;
background-color: olive;
}
.child {
width: 50px;
height: 50px;
background-color: skyblue;
}
// js
var parent = document.querySelector('.parent')
var child = document.querySelector('.child')
parent.addEventListener('click', function(e){
console.log('parent click', e.currentTarget.className, e.target.className);
})
child.addEventListener('click', function(e){
console.log('child click', e.currentTarget.className, e.target.className);
})
tips
: 可能有人对于e.currentTarget
和e.target
之间的区别不太清楚,只要记住一点即可:e.currentTarget
返回绑定事件的元素,e.target
返回触发事件的元素,只有触发元素和绑定元素为同一元素,则二者才指向同一元素
效果如下:
点击 parent
元素,控制台打印 parent
点击事件的输出,这很合理,但是点击 child
元素的时候,则是先打印出 child
点击事件输出,又打印出 parent
点击事件输出。
了解过 js
事件机制的同学应该都清楚后一种情况发生的原因:js
事件分为 事件冒泡 和 事件捕获,addEventListener
这个函数实际上有三个参数,前两个参数为必选,第三个参数为可选,指定使用冒泡机制还是捕获机制,默认为 false
,表示使用事件冒泡机制。
这里 child
使用默认的事件冒泡机制,所以 click
点击事件先触发 child
的事件函数,接着向上冒泡到达 parent
,触发 parent
的事件函数,因为事件的触发元素依旧是 child
,所以 parent
事件的 e.target
是 child
想要阻止事件冒泡,可在 child
的事件函数中加入下面这句:
e.stopPropagation()
- 设置事件
onclick
把上面的 js
代码换成下面这种:
parent.onclick=function(e) {
console.log('parent click', e.currentTarget.className, e.target.className);
}
child.onclick=function(e) {
console.log('child click', e.currentTarget.className, e.target.className);
}
效果:
可以看到,效果和 addEventListener
是一样的,为 child
事件增加阻止冒泡的代码,同样可以阻止事件从 child
向 parent
传播。
唯一的区别在于,使用 addEventlistener
,可以重复多次监听同一个事件,但是 onclick
就只能监听一次,多次设置则后面的覆盖掉前面的,当然,这与事件本身没什么关系,主要是由 addventListener
的特性决定的。
- 在元素的标签上显性声明事件
修改 DOM
如下:
<div class="parent" onclick="parentClick()">parent
<div class="child" onclick="childClick()">child</div>
</div>
JS
如下:
function parentClick() {
console.log('parent click');
}
function childClick() {
console.log('child click');
}
效果如下:
效果似乎和上面两个都是一样的,但是有一点不同,那就是这种直接为标签添加事件的方法,没办法传递 event
事件参数,所以也就无法阻止 child
的事件向上冒泡。
vue
中的click
用 vue
的方式为一个元素添加 click
事件,可以像下面这样:
// HTML
<div id='app' class="parent" @click="parentClick">parent
<div class="child" @click="childClick">child</div>
</div>
// JS
new Vue({
el: '#app',
methods: {
parentClick: function (e) {
console.log('parentClick', e.currentTarget.className, e.target.className);
},
childClick: function (e) {
// e.stopPropagation()
console.log('childClick', e.currentTarget.className, e.target.className);
}
}
})
效果如下:
这种方式是直接在标签上增加声明事件的,乍一看好像和第三种方式有点像,但是却能够使用 e.stopPropagation()
来阻止事件冒泡,实际上, 这种方法的本质是利用了 addEventListener
,vue
中绑定事件的源码如下:
react
中的click
// HTML
<div id="app"></div>
// JS
class Box extends React.Component {
constructor(props) {
super(props)
}
handleClick(type, e) {
e.stopPropagation()
console.log(type);
}
render() {
return (
<div className="parent" onClick={this.handleClick.bind(this, 'parentClick')}>parent
<div className="child" onClick={this.handleClick.bind(this, 'childClick')}>child</div>
</div>
);
}
}
ReactDOM.render(
<Box />,
document.getElementById('app')
);
效果和 vue
的一样,都是基于 addEventListener
实现的:
本文虽然仅仅是拿 click
事件来进行举例,但本质上还是想通过 click
这一个具体的事件来扩展到其他诸如 mouseover
、doubleclick
等事件,事件的本质基本上都一样,通过一个事件自然也就能概括得到其他的事件特性。