场景:一个页面中的导航栏 / 图片下拉框,具有多个相同的标签节点,可以获取点击的某一个,并封装对应的方法函数(不考虑 IE)
需要考虑的步骤:
(1)事件代理的原理(事件冒泡)
(2)函数中需要考虑的几点(默认行为,this 指向)
1. 事件捕获和事件冒泡
DOM 是一种树形结构,事件会在元素结点和根结点之间传递,途经的结点会收到该事件,传递过程可以称为事件流。
事件流分为三个阶段:
- 捕获阶段
- 目标阶段
- 冒泡阶段
事件冒泡会从当前触发的事件目标一级一级往上传递,依次触发,直到 document 为止。
事件捕获会从 document 开始触发,一级一级往下传递,依次触发,直到真正事件目标为止。
window->document->HTML->body-> 父元素 -> 子元素
2. 事件代理
(1)原理:事件冒泡机制。当子结点触发事件时,事件流会向父节点传播,并触发父节点上的事件。
(2)优点:
- 代码简洁,当动态增加结点时,无需做额外操作
- 减少绑定注册事件,减少浏览器内存消耗
3. 函数实现
(1)实现一次事件冒泡
<div id="para">
<ul id="contain">
<li id="p1"> 标签 1 </li>
<li id="p2"> 标签 2 </li>
<li id="p3"> 标签 3 </li>
</ul>
</div>
/*
*
* 事件冒泡
*
*/
function bindEvent(elem, type, fn) {elem.addEventListener(type, fn);
}
var paras = document.getElementById('para')
var contains = document.getElementById('contain')
var p1s = document.getElementById('p1')
console.log(paras, contains, p1s)
bindEvent(p1s, 'click', function(e) {e.preventDefault()
// 阻止事件冒泡 e.stopPropagation() e.cancelBubble = true IE
console.log('p')
})
bindEvent(paras, 'click', function(e) {console.log('para')
})
可以看到,当点击标签的时候,结点的父元素也触发了对应的方法。
(2)实现事件代理
/*
*
* 事件代理
*
*/
function eventProxy(elem, type, proxyElem, fn) {if(fn === null) { // 不使用事件代理
fn = proxyElem;
proxyElem = null;
}
elem.addEventListener(type, function(e) {
var target;
if(proxyElem) { // 使用事件代理的情况
target = e.target;
console.log(target)
if (target.matches(proxyElem)) {fn.call(target, e); // 改变函数 this 执行上下文到目标结点
}
} else { // 不使用事件代理的情况
fn(e);
}
})
}
var proxyContainer = document.getElementById('proxy');
eventProxy(proxyContainer, 'click', 'a' , function(e) {e.preventDefault();
console.log(this.innerHTML); // 获取当前结点信息
})
可以看到,通过事件代理的方式,将事件绑定在父节点上,并可以区分被点击分子节点的信息。