浏览器的事件处理

事件流

可以想象画在一张纸上的一组同心圆,如果把手指放在圆心上,那么手指指向的不是一个圆,而是纸上所有的圆。

事件流描述的是从页面中接受事件的顺序。

触发顺序,先捕获,后冒泡

事件冒泡

即事件开始时由最具体的元素接受,然后逐级向上传播到较为不具体的节点。

结构上(非视觉上)嵌套关系的元素,会存在事件冒泡的功能,即同一事件,自子元素冒泡向父元素。(自底向上)。

focus,blur,change,submit,reset,select 等事件不冒泡。

比如下面这个例子,点击.box,会先后打印出 boxcontentwrapper

1
2
3
4
5
<div class="wrapper">
<div class="content">
<div class="box"></div>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
let wrapper = document.getElementsByClassName('wrapper')[0];
let content = document.getElementsByClassName('content')[0];
let box = document.getElementsByClassName('box')[0];

wrapper.addEventListener('click', function () {
console.log('wrapper')
}, false)
content.addEventListener('click', function () {
console.log('content')
}, false)
box.addEventListener('click', function () {
console.log('box')
}, false)

事件捕获

结构上(非视觉上)嵌套关系的元素,会存在事件捕获的功能,即同一事件,自父元素捕获至子元素。(自顶向下)。

下面的代码与事件冒泡的代码的不同就是 false 改成了 true,会先后打印出 wrappercontentbox

1
2
3
4
5
6
7
8
9
wrapper.addEventListener('click', function () {
console.log('wrapper')
}, true)
content.addEventListener('click', function () {
console.log('content')
}, true)
box.addEventListener('click', function () {
console.log('box')
}, true)

结合

先捕获,后冒泡,这里注意一下 boxBubblebox 的打印顺序,因为 boxBubble 是先绑定的,所以先执行。

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
26
27
28
// 冒泡
wrapper.addEventListener('click', function () {
console.log('wrapperBubble')
}, false)
content.addEventListener('click', function () {
console.log('contentBubble')
}, false)
box.addEventListener('click', function () {
console.log('boxBubble')
}, false)

// 捕获
wrapper.addEventListener('click', function () {
console.log('wrapper')
}, true)
content.addEventListener('click', function () {
console.log('content')
}, true)
box.addEventListener('click', function () {
console.log('box')
}, true)

// wrapper
// content
// boxBubble
// box
// contentBubble
// wrapperBubble

取消冒泡

此时控制台只会打印出 boxBubble

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
wrapper.addEventListener('click', function () {
console.log('wrapperBubble')
}, false)
content.addEventListener('click', function () {
console.log('contentBubble')
}, false)
box.addEventListener('click', function (e) {
console.log('boxBubble')
stopBubble(e);
}, false)

function stopBubble(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}

阻止默认事件

  1. return false; 以对象属性的方式注册的事件才生效
  2. event.preventDefault(); W3C标注,IE9以下不兼容
  3. event.returnValue = false; 兼容IE
1
2
3
4
5
6
7
8
9
10
11
12
13
// 第一种方法 return false 对 addEventListener 不生效
document.oncontextmenu = function (e) {
console.log('a');
cancelHandler(e);
}

function cancelHandler(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}

阻止 a 标签的默认事件,点击会回到顶部。

1
2
<!-- void相当于return -->
<a href="javascript:void(false)">百度一下</a>

事件对象

event || window.event

事件源对象:

  1. event.target 火狐只有这个
  2. event.srcElement IE只有这个
  3. chrome上面的都有
1
2
3
4
5
6
7
// 获取事件源对象
let wrapper = document.getElementsByClassName('wrapper')[0];
wrapper.onclick = function (e) {
let event = e || window.event;
let target = event.target || event.srcElement;
console.log(target);
}

事件委托

事件冒泡 + 事件源对象结合,避免过多地遍历 li。

优点:

  1. 性能:不需要循环所有的元素一个个绑定事件
  2. 灵活:当有新的子元素时不需要重新绑定事件
1
2
3
4
5
6
7
8
9
10
11
12
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
</ul>
1
2
3
4
5
6
let ul = document.getElementsByTagName('ul')[0];
ul.onclick = function (e) {
let event = e || window.event;
let target = event.target || event.srcElement;
console.log(target.innerText)
}