Node.once会导致target在销毁时的自动off失效

  • Creator 版本: 2.4.9

  • 目标平台:Chrome, Android

  • 重现概率:必现

node.on如果有target参数时,当target销毁时,node会自动off之前设置的callback,防止调用一个已经失效的对象。

但是如果在这个node上,再调用一次node.once,并且同样传入target,则node自动off会失效,导致callback在已经销毁的target被调用。

我在浏览器里单步跟踪的时候,看到的是CCNode.js的once函数里面的匿名函数,会把target的__eventTargets的元素错误地多remove了一次,导致自动off失效

重现代码如下,将代码放入一个ts文件,然后挂在一个空场景的node里运行即可

const {ccclass, property} = cc._decorator;

export class EvtHub{
    private static _node : cc.Node = null;
    static on<T extends Function>(type: string, callback: T, target?: any, useCapture?: boolean): T{
        const nd = EvtHub.getNode();
        return nd.on(type, callback, target, useCapture);
    }

    static off(type: string, callback?: Function, target?: any, useCapture?: boolean): void{
        const nd = EvtHub.getNode();
        return nd.off(type, callback, target, useCapture);
    }

    static once<T extends Function>(type: string, callback: T, target?: any, useCapture?: boolean): T{
        const nd = EvtHub.getNode();
        return nd.once(type, callback, target, useCapture);
    }

    static emit(type: string, arg1?: any, arg2?: any, arg3?: any, arg4?: any, arg5?: any): void{
        const nd = EvtHub.getNode();
        return nd.emit(type, arg1, arg2, arg3, arg4, arg5);
    }

    static getNode():cc.Node{
        if(EvtHub._node == null ){
            let nd = EvtHub._node = new cc.Node();
            nd.name = "Hub";
            cc.game.addPersistRootNode(nd);
        }
        return EvtHub._node;
    }
}

@ccclass
export default class EvtOnceBug extends cc.Component{
    onLoad(){
        // EvtHub.on('PING', this._ping.bind(this)); //BAD, cannot auto off
        EvtHub.on('PING', this._ping, this); //GOOD, can auto off
        EvtHub.once('PING', this.call_once, this); //will mess up auto-off
    }
    start(){
        setInterval(()=>EvtHub.emit('PING'), 1000);
        this.schedule( ()=> { this.node.destroy() }, 0, 0, 4.5 );
    }
    private _ping(){
        if (!cc.isValid(this.node)){
            cc.log("INVALID node", this.node);
        }
        cc.log(`ping ${cc.director.getTotalFrames()}`, this);
    }
    private call_once(){
        cc.log("one time");
    }
}
1赞