JsWeb前端之路让前端飞

前端面试题——事件代理 delegate 的实现(二)

2017-07-01  本文已影响71人  ac68882199a1

上一篇说了事件代理的原理,小伙伴们可以猛戳下面的链接直接查看

前端面试题——事件代理 delegate 的实现(一)

在结尾的地方,留下了一个问题:如何卸载已经绑定的代理事件

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)

但是匿名函数是真的没办法移除啊 :)

如果你觉得这篇文章还不错的话,就请关注一下前端周记公众号吧!感谢你的支持!文中有任何错误都欢迎指正!

扫码关注前端周记公众号
上一篇下一篇

猜你喜欢

热点阅读