前端面试题——事件代理 delegate 的实现(二)
2017-07-01 本文已影响71人
ac68882199a1
上一篇说了事件代理的原理,小伙伴们可以猛戳下面的链接直接查看
在结尾的地方,留下了一个问题:如何卸载已经绑定的代理事件
javascript 除了给了我们一个添加事件监听器的 api addEventListener
外,还给了一个移除的 api removeEventListener
,下面先来看一下这个 api 是怎么使用的
el.removeEventListener(event, functionName)
移除事件监听的 api 需要两个参数,第一个与添加监听的参数相同——事件类型,第二个参数为需要移除的函数名,这就意味着匿名函数无法被移除
上一节中,添加的所有回调函数都是匿名函数,如果需要移除,则需要将它们先改为具名函数,如下:
// 事件回调
function callback () {...}
// 添加事件监听
el.addEventListener('click', callback)
// 移除监听
el.removeEventListener('click', callback)
通过上面这种方法,我们能够成功移除绑定在元素el
上的事件,但是如果想要移除通过代理的方式绑定的事件,通过这个 api 是无法实现的,因为这个 api 仅能移除直接绑定在该元素上的事件
那么通过代理的方式实现的事件监听,该如何移除呢?其实也不麻烦,我们只需要实现一个自己的事件代理即可
实现自己的事件代理
代码是最好的语言。。。所以直接上代码,需要注意的是,以下代码仅通过标签获取元素,如果你想要通过 id class 等等其他方式,可以自己踩坑哦~
// 记录当前这所有的代理方法
let actionsCollection = []
class Delegate {
constructor (tagName) {
if (!tagName) throw new Error('tagName is required')
this.tag = tagName.toUpperCase()
this.el = document.querySelector(tagName)
this.el ? void 0 : throw new Error('tagName must be an exist element')
}
// type 事件类型
// aimTagName 需要监听事件的标签
// action 回调函数
on (type, aimTagName, action) {
if (typeof action !== 'function') {
throw new Error('action must be an function')
return
}
const funcName = action.name
const aimTag = aimTagName ? aimTagName.toUpperCase() : this.tag // 如果 aimTagName 不存在 则绑定到自身
// 如果不是匿名函数 则记录当前的事件
// tag 该事件绑定的标签
// type 记录触发类型
if (funcName) {
actionsCollection.push({
name: funcName,
tag: aimTag,
type: type
})
}
this.el.addEventListener(type, e => {
if (e.target && e.target.nodeName.toUpperCase() === aimTag) {
if (!funcName) { // 匿名函数 直接执行 不需要判断是够已经移除
action (e)
}
else { // 处理非匿名函数 函数可以被移除
// 由于一个函数可能被用于多个标签上 所以此处要遍历所有的代理方法
actionsCollection.map(func => {
if (func.name === funcName &&
func.tag === aimTag &&
func.type === type) action (e)
})
}
}
})
}
// type 事件类型
// actionName 需要移除的函数名
off (type, actionName) {
let temp = []
// 不传入参数 将移除改元素上所有的事件
if (!arguments.length) {
actionsCollection.map(func => {
if (func.tag !== this.tag) temp.push(func)
})
} else {
// 如果只传入 type 将移除这个元素上的所有 type 事件
if (!actionName) {
actionsCollection.map(func => {
if (func.tag !== this.tag ||
func.type !== type) temp.push(func)
})
} else {
// 同时传入两个参数 将移除这个元素上对应 type 的对应事件
actionsCollection.map(func => {
if (func.tag !== this.tag ||
func.type !== type ||
func.name !== actionName) temp.push(func)
})
}
}
actionsCollection = temp
}
}
// 不使用 new 关键字创建绑定对象
function D (tagName) {
return new Delegate(tagName)
}
使用方式
嗯,如果你用过(你肯定用过)jQuery 的话,那对你来说一定很简单,因为就是模仿的 jQuery 的 api
D(elTag).on(event, aimElTag, callback)
D(elTag).off(event, callbackName)
但是匿名函数是真的没办法移除啊 :)
如果你觉得这篇文章还不错的话,就请关注一下前端周记公众号吧!感谢你的支持!文中有任何错误都欢迎指正!
扫码关注前端周记公众号