上篇文章我们提到,注册事件的方法addEventListener()内有三个参数,分别是「事件名称」、「事件的处理程序」,以及「捕获」或「冒泡」的机制切换。

那么,今天我们要来看的第一个部分,就是隐藏在事件处理程序中的event

隐藏在监听函数中的”event”

当监听的事件发生时,浏览器会去执行我们通过addEventListener()注册的事件处理程序函数。

这个时候,EventListener 会去创建一个「事件对象」 (Event Object),里面包含了所有与这个事件相关的属性,并且以「参数」的形式传给我们的处理程序函数:

<button id="btn">Click</button>
......
var btn = document.getElementById('btn');

// 参数 e 就是上面所说的事件对象
// 因为是参数,当然可以自己定名称
btn.addEventListener('click', function(e){
console.log(e);
}, false);

当点击<button>后,可以从console看到event对象中提供了这么多东西:

像是

  • type : 表示事件的名称
  • target : 表示触发事件的元素
  • bubbles:表示这事件是否是在「冒泡」阶段触发( true/ false)
  • pageX/ pageY:表示事件触发时,鼠标座标在网页的相对位置

其余的属性这里就不一一介绍,不过要注意的是,每个「事件对象」所提供的属性都会根据触发的事件而稍微不同。

event. preventDefault()

HTML中部分元素会有默认行为,像是<a>标签默认页面跳转或是锚点定位,或是表单的submit等等…

如果我们需要在这些元素上绑定事件,那么适当地取消它们的默认行为就是很重要的一件事。

比如,有一个通往baidu的链接<a>:

<a id="link" href="https://www.baidu.com">百度</a>

假设今天点击这个link时,我希望浏览器执行console.log('米淇淋你好帅!');那么根据先前所说,我可以先注册click事件:

var link = document.querySelector('#link');

link.addEventListener('click', function (e) {
console.log('米淇淋你好coll!');
}, false);

结果你却发现,即便我们在<a>中去注册了click事件,但是当我点击这个link的时候,浏览器开始会console.log出”米淇淋你好帅!”,但最后baidu的网页依旧会覆盖我想要的内容。

可是我希望执行的是console.log('米淇淋你好帅!');而不是直接把我带偏了去到baidu的网站,那么我们该怎么做,才能避免呢?

这时候如果调用event.preventDefault()方法,默认事件行为将不再触发

var link = document.querySelector('#link');

// 在 事件处理函数中 加上 e.preventDefault();
link.addEventListener('click', function (e) {
e.preventDefault();
console.log('米淇淋你好帅!');
}, false);

这个时候,再试着点击link一次,你会发现浏览器默认的跳转页面的行为不见了,console.log('米淇淋你好帅!');也可顺利执行啦哈哈。

但要注意的是,event.preventDefault()并不会阻止事件向上传递(即事件冒泡) 。

另外,值得一提的是,下面这样设置也可以让a标签仅仅当做一个普通的按钮,点击实现一个功能,不想页面跳转,也不想锚点定位:

<a href="javascript:;">链接</a>

此外,在事件处理函数的最后加上return false;也会有event.preventDefault()的效果,但切记不可以加在前面,若是加在前面事件处理函数就直接gg了。

event.stopPropagation() & event.stopImmediatePropagation()

1.event.stopPropagation() 方法阻止事件向上冒泡传递,阻止任何父事件处理程序被执行。

接下来我们看个例子:

<div>
<div id="parent">
父元素
<div id="child">子元素</div>
</div>
</div>
......
var parent = document.getElementById('parent');
var child = document.getElementById('child');

child.addEventListener('click', function () {
console.log('child bubbling');
}, false);

parent.addEventListener('click', function () {
console.log('parent bubbling');
}, false);

document.body.addEventListener('click', function () {
console.log('body bubbling');
}, false);

document.documentElement.addEventListener('click', function () {
console.log('html bubbling');
}, false);

document.addEventListener('click', function () {
console.log('document bubbling');
}, false);

window.addEventListener('click', function () {
console.log('window bubbling');
}, false);

当我点击的是「子元素」的时候,通过console.log可以观察到事件触发的顺序为:

child bubbling
parent bubbling
body bubbling
html bubbling
document bubbling
window bubbling

而如果在「子元素」中加入event.stopPropagation() 方法,其余保持原样的话:

child.addEventListener('click', function (e) {
console.log('child bubbling');
e.stopPropagation();
}, false);

再次点击「子元素」,则只出现:

child bubbling

其余父事件不会触发,即event.stopPropagation() 方法阻止了事件向上冒泡传递,阻止任何父事件处理程序被执行。

2.stopImmediatePropagation()方法 既能阻止事件向父元素冒泡,也能阻止元素同事件类型的其它监听器被触发。而 stopPropagation 只能实现前者的效果。

我们来看个例子:

<body>
<button id="btn">click me to stop propagation</button>
</body>
......
var btn = document.querySelector('#btn');

btn.addEventListener('click', function(e) {
console.log('btn click 1');
//e.stopImmediatePropagation();
});

btn.addEventListener('click', function() {
console.log('btn click 2');
});

document.body.addEventListener('click', function() {
console.log('body click');
});

document.documentElement.addEventListener('click', function() {
console.log('html click');
});

document.addEventListener('click', function() {
console.log('document click');
});

window.addEventListener('click', function() {
console.log('window click');
});

当我点击button的时候,通过console.log可以观察到事件触发的顺序为:

btn click 1
btn click 2
body click
html click
document click
window click

而如果在「btn的第一个监听函数」中加入event.stopImmediatePropagation() 方法,其余保持原样的话:

btn.addEventListener('click', function(e) {
console.log('btn click 1');
e.stopImmediatePropagation();
});

再次点击button,则只出现:

btn click 1

所以说,使用 stopImmediatePropagation() 方法后,点击按钮时,仅触发设置了stopImmediatePropagation() 方法的监听器,与此同时按钮的其余同类型点击事件不触发。

event.target & event.currentTarget

老实说并不能好好用文字描述这两者的区别,我们直接看个例子:

<style>
#a{
width: 200px;
height: 200px;
background: yellow ;
}
#b{
width: 150px;
height: 150px;
background: green;
}
#c{
width: 100px;
height: 100px;
background: grey;
}
#d{
width: 50px;
height: 50px;
background: black;
}
</style>
......
<div id="a">
<div id="b">
<div id="c">
<div id="d"></div>
</div>
</div>
</div>
......
document.getElementById('a').addEventListener('click', function(e) {
console.log(
'target:' + e.target.id + '&currentTarget:' + e.currentTarget.id
)
})
document.getElementById('b').addEventListener('click', function(e) {
console.log(
'target:' + e.target.id + '&currentTarget:' + e.currentTarget.id
)
})
document.getElementById('c').addEventListener('click', function(e) {
console.log(
'target:' + e.target.id + '&currentTarget:' + e.currentTarget.id
)
})
document.getElementById('d').addEventListener('click', function(e) {
console.log(
'target:' + e.target.id + '&currentTarget:' + e.currentTarget.id
)
})

jsbin 点这里。

当我们点击最里层黑色区域的元素d的时候,会依次输出:

target:d&currentTarget:d
target:d&currentTarget:c
target:d&currentTarget:b
target:d&currentTarget:a

从输出中我们可以看到,event.target指向引起触发事件的元素,而event.currentTarget则是事件绑定的元素,只有被点击的那个目标元素的event.target才会等于event.currentTarget也就是说,event.currentTarget始终是监听事件者,而event.target是事件的真正发出者

另外,值得一提的是,function内部的this指的也就是event.currentTarget

事件代理

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件代理或叫事件委托(Event Delegation)。

1.优点

  • 减少内存消耗,提高性能

假设有一个列表,列表之中有大量的列表项,我们需要在点击每个列表项的时候响应一个事件:

<ul id="myList">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
......
<li>Item n</li>
</ul>

如果给每个列表项都绑定一个函数,假如此时列表项很多,那无疑对内存的消耗是非常大的,并且效率上需要消耗很多性能。借助事件代理,我们只需要给父容器 ul 绑定方法即可,这样不管点击的是哪一个后代元素,都会根据冒泡传播的传递机制,把容器的click行为触发,然后把对应的方法执行,根据事件源,我们可以知道点击的是谁,从而完成不同的事。

  • 动态绑定事件

在很多时候,我们需要通过用户操作动态的增删列表项元素,如果一开始给每个子元素绑定事件,那么在列表发生变化时,就需要重新给新增的元素绑定事件,给即将删去的元素解绑事件,如果用事件代理就会省去很多这样麻烦。

2.如何实现

接下来我们来实现上例中父层元素 #myList 下的 li 元素的事件委托到它的父层元素上:

// 取得容器
var myList = document.getElementById('myList');

// 让父层 myList 来监听 click 事件
myList.addEventListener('click', function(e){

// 判断目标元素若是 li 则执行 console.log
if( e.target.tagName.toLowerCase() === 'li' ){
console.log(e.target.textContent);
}
}, false);


// 建立新的 <li> 元素
var newList = document.createElement('li');

// 建立 textNode 文字节点
var textNode = document.createTextNode("Hello world!");

// 通过 appendChild 将 textNode 加入至 newList
newList.appendChild(textNode);

// 通过 appendChild 将 newList 加入至 myList
myList.appendChild(newList);

我们把click事件改由父层的myList来监听,利用事件传递的原理,判断e.target是我们想要的目标节点时,才去执行后续的动作。

这样的好处是你的事件管理会非常轻松,而且后续加上的newList也会有click的效果,无需另外再去绑定click事件。

如果觉得文章对你有些许帮助,欢迎在我的GitHub博客点赞和关注,感激不尽!



JavaScript  

JavaScript 事件 event

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!