文章结构

  • 事件绑定
  • 事件冒泡
  • 事件代理(事件委托)

事件绑定

普通的事件绑定方式如下:

1
2
3
4
5
6
7
var div1 = document.getElementById('div1');

div1.addEventListener('click', function(e) {
// e.preventDefault() 阻止默认行为
// e.stopPropagation() 阻止事件冒泡
console.log('clicked');
});

可以对上述事件绑定编写一个更通用的方法:

1
2
3
function bindEvent(el, event, fn) {
el.addEventListener(event, fn);
}

调用该方法:

1
2
3
4
5
var div1 = document.getElementById('div1');

bindEvent(div1, 'click', function(e) {
console.log('clicked');
});

事件冒泡

在 DOM 树上绑定的事件,会沿着 DOM 结构冒泡,从下到上挨个(直到 body)执行。使用 e.stopProgapation()可以阻止事件冒泡。

事件代理(事件委托)

应该添加到子元素上的事件,添加到了父元素上。例如:

1
2
3
4
5
6
7
<div id="div1">
<a href="#">a1</a>
<a href="#">a2</a>
<a href="#">a3</a>
<a href="#">a4</a>
</div>
<button>点击增加一个 a 标签</butto

这里,一个<div>中包含很多个<a>,并且将来还有可能继续添加<a>。那么怎么快速方便地给<a>添加事件呢?

这里我们就可以采用事件代理的方式,监听<a>上的事件,但把具体的事件绑定到<div>上。代码如下:

1
2
3
4
5
6
7
8
var div1 = document.getElementById('div1');

div1.addEventListener('click', function(e) {
var target = e.target;
if (e.nodeName === 'A') {
console.log(target.innerHtml);
}
});

再完善一下之前封装得方法,让它支持事件代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function bindEvent(el, event, selector, fn) {
// 这样处理,可接收两种调用方式 bindEvent(div1, 'click', 'a', function () {...}) 和 bindEvent(div1, 'click', function () {...})
if (fn == null) {
fn = selector;
selector = null;
}

// 绑定事件
el.addEventListener(event, function(e) {
var target;
if (selector) {
// 有 selector 说明需要事件代理
// 获取触发事件的元素,即 e.target
target = e.target;
// 看是否符合 selector 这个条件
if (target.match(selector)) {
// call方法主要作用是改变函数的上下文同时执行构造方法,第一个参数是对应的上下文对象。例如:fn.call({a: 100})就是把fn的this指向{a: 100}
fn.call(target, e);
}
} else {
// 无 selector 则不需要事件代理
fn(e);
}
});
}

改进之前的绑定代码:

1
2
3
4
5
6
7
8
9
10
var div1 = document.getElementById('div1');
var a1 = document.getElementById('a1');
// 事件绑定
bindEvent(div1, 'click', function(e) {
console.log(a1.innerHTML);
});
// 事件代理
bindEvent(div1, 'click', 'a', function(e) {
console.log(this.innerHTML);
});

使用事件代理的优点:

  • 代码看起来更简洁
  • 减少浏览器的内存占用