-
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");
}
}