冒泡捕获流、事件与事件源对象、事件委托
冒泡、捕获
事件冒泡
从DOM结构来看,由最里层一层一层的把事件向外传递的现象,叫做冒泡现象。
例如,同时为父子元素添加click的事件处理函数,点击子元素时,父级元素的事件处理函数也会被触发,这就是一种冒泡现象。事件向父级冒泡,然后触发冒泡对应的事件处理函数。
事件捕获
从DOM结构来看,由最外层向最里层把事件向内传递的现象,叫做捕获现象。
总体来说,自嵌套关系最顶层的父级元素开始捕获事件,直到事件源的子元素,事件捕获完成。可以设置addEventListener的第三个参数为true,就是事件捕获,值为false,就是事件冒泡。
下面通过一个案例来观察捕获和冒泡行为。
index.html
<div class="wrapper">
<div class="outer">
<div class="inner"></div>
</div>
</div>
index.css
.wrapper {
width: 300px;
height: 300px;
background-color: green;
}
.wrapper .outer {
width: 200px;
height: 200px;
background-color: orange;
}
.wrapper .outer .inner {
width: 100px;
height: 100px;
background-color: blue;
}
index.js
var wrapper = document.getElementsByClassName('wrapper')[0],
outer = wrapper.getElementsByClassName('outer')[0],
inner = outer.getElementsByClassName('inner')[0];
wrapper.addEventListener('click', function () {
console.log('wrapper');
}, false);
outer.addEventListener('click', function () {
console.log('outer');
}, false);
inner.addEventListener('click', function () {
console.log('inner');
}, false);
点击inner盒子,参数为false,冒泡行为时,打印如下信息。
inner
outer
wrapper
点击inner盒子,参数为true,捕获行为时,打印如下信息。
wrapper
outer
inner
由此可以看出冒泡和捕获的执行现象,捕获是从顶层元素到里层元素,冒泡是从里层元素到顶层元素。
此外,两种现象是可以同时触发的,事件捕获是先执行的,事件冒泡是后执行的。事件捕获作用到事件源上时,不存在捕获现象,正常执行函数,不存在冒泡和捕获谁先谁后问题。
outer.addEventListener('click', function () {
console.log('bubble outer');
}, false);
outer.addEventListener('click', function () {
console.log('outer');
}, true);
打印:
bubble outer
outer
focus、blur、change、submit、reset、select没有冒泡和捕获现象。
IE浏览器没有事件捕获现象,老版本除chrome浏览器之外,没有事件捕获。
事件流
事件流:用来描述从页面中接收事件的顺序,与冒泡和捕获行为相关。
微软IE提出事件冒泡流(Event Bubbling)。
Netscape(网景)提出事件捕获流(Event Captureing)。
事件流分为3个阶段:
- 事件捕获阶段
- 处于目标阶段(事件源所绑定的事件处理函数触发)
- 事件冒泡阶段
DOM事件级别:
DOM事件级别是对事件类型的定义,是不同时期的DOM事件规范。
1. DOM0级
定义on之类的事件模型(onmouseover、onmouseout),以句柄的方式绑定事件处理函数。
以onlcik为例,支持两种写法。
- 元素属性 onclick=""
- Element.onclick = function () {}
2. DOM1级
没有定义事件模型。
3. DOM2级
定义addEventListener()、removeEventListener()、使用3个参数,成为W3C规范。
IE9以下,没有遵守规范,必须用attachEvent、detachEvent绑定事件处理函数。
4. DOM3级
对于DOM2级的扩展,增加更多的事件类型(load、scroll、keydown、keyup等)。
允许用户自定义事件。
事件与事件源对象
一旦对某个元素进行某一事件的触发,那么浏览器就会把把这个事件触发以后的详细信息包装成一个对象(e、ev、event),传递到事件处理函数的参数中去。
IE不是传到事件处理函数的参数中,是传到window.event中。
target、srcElement就是事件源对象。
FF(火狐)浏览器只有target属性;
IE浏览器只有srcElement属性;
chrome浏览器两个属性都有;
为此,编写程序时,可以做以下兼容性处理
btn.onclick = function (ev) {
var e = ev || window.event,
tar = e.target || e.srcElement;
console.log(e);
}
事件委托/事件代理
把事件委托给父级,事件被触发后会冒泡到父级,点击子元素时,父级元素也能得到被点击的事件源对象,根据事件源对象做出相应处理,
事件代理的好处:
- 减少多次绑定事件处理函数,对性能优化较好。
- 对于动态增加的子元素,可以获取到事件源对象。
- 可以动态获取子元素下标。
下面具体以一个案例来演示事件代理
index.html
<button>添加li元素</button>
<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>
index.js
var oList = document.getElementsByTagName('ul')[0],
oLi = oList.getElementsByTagName('li'),
oBtn = document.getElementsByTagName('button')[0];
oList.onclick = function (ev) {
var e = ev || window.event,
tar = e.target || e.srcElement,
tagName = tar.tagName.toLowerCase(),
idx = [].indexOf.call(oLi, tar);
if (tagName === 'li') {
console.log(tar, tar.innerText, tagName, idx);
}
}
oBtn.onclick = function () {
var li = document.createElement('li');
li.innerText = oLi.length + 1;
oList.appendChild(li);
}
使用事件代理可以避免重复为li元素绑定多个事件处理函数,有效减少性能开销。