事件触发 currentTarget 却为 null

1,848 阅读2分钟

下午写代码遇到一个奇怪的问题,事件触发后处理函数获取 currentTarget 却为 null。

后来发现,问题出在处理函数是异步访问 currentTarget 上,具体解释前,需要先知道 event 对象中 currentTarget 的“生命周期”。看下面的简单例子:

class Event {
    constructor(type, target) {
        this.type = type
        this.target = target
        this.currentTarget = null
    }

    static fire(type, target, currentTarget) {
        const event = new Event(type, target)
        // 事件触发时, 将 currentTarget 绑定到事件对象中
        event.currentTarget = currentTarget
        // 执行监听的函数
        event.currentTarget.eventListener[type].forEach(fn => fn(event))
        // 事件处理结束后,再将 currentTarget 移除
        event.currentTarget = null
    }
}

事实上,currentTarget 只存在于事件触发到事件处理结束之间,随后就重置为 null 了

所以,当我们有下面一段处理函数时

const handler = (e) => {
    console.log('handling, currentTarget = ', e.currentTarget)
    // 异步访问 currenTarget, 此时事件处理已结束, currentTarget 被重置为 null
    setTimeout(() => console.log('timeup,', 'currentTarget = ', e.currentTarget), 0)
}

控制台输出结果如下:

// 控制台输出
// handling, currentTarget =  Target { name: 'currentTarget'} }
// timeup, currentTarget =  null

同步的代码能够顺利访问到 currentTarget 而异步代码则为 null

解决的方案很简单,就是把 currentTarget 保存起来,所以处理函数可以这样修改:

const handler = (e) => {
    const currenTarget = e.currenTarget
    // 异步访问 currenTarget, 此时事件处理已结束, currentTarget 被重置为 null
    setTimeout(() => console.log('timeup,', 'currentTarget = ', currenTarget), 0)
}

问题解决~

总结:当事件处理函数中存在异步操作时,需要将 currentTarget 保存以便异步操作顺利访问


完整的测试代码:

const { ENETUNREACH } = require("constants")

class Event {
    constructor(type, target) {
        this.type = type
        this.target = target
        this.currentTarget = null
    }

    static fire(type, target, currentTarget) {
        const event = new Event(type, target)
        // 事件触发时, 将 currentTarget 绑定到事件对象中
        event.currentTarget = currentTarget
        // 执行监听的函数
        event.currentTarget.eventListener[type].forEach(fn => fn(event))
        // 事件处理结束后,再将 currentTarget 移除
        event.currentTarget = null
    }
}

class Target {
    constructor(name) {
        this.name = name
        this.eventListener = { 'click': [] }
    }
    addEventListener(type, fn) {
        this.eventListener[type]
            && this.eventListener[type].push(fn)
    }
}


const handler = (e) => {
    console.log('handling, currentTarget = ', e.currentTarget)
    
    // 异步访问 currenTarget, 此时事件处理已结束, currentTarget 被重置为 null
    setTimeout(() => console.log('timeup,', 'currentTarget = ', e.currentTarget), 0)
}

const currentTarget = new Target('currentTarget')
const target = new Target('target')
currentTarget.addEventListener('click', handler)

Event.fire('click', target, currentTarget)
// 控制台输出
// handling, currentTarget =  Target { name: 'currentTarget', eventListener: { click: [ [λ: handler] ] } }
// timeup, currentTarget =  null