关于_onPreDestroy中target.targetOff(this)的BUG

  • Creator 版本:2.1.3

  • 目标平台: Web 只在浏览器测试,其他平台没测试

  • 详细报错信息,包含调用堆栈:

  • 重现方式:退出场景

  • 之前哪个版本是正常的:无

  • 出现概率:100%

  • 额外线索:
    var eventTargets = this.__eventTargets;
    for (var i = 0, l = eventTargets.length; i < l; ++i) {
    var target = eventTargets[i];
    target && target.targetOff(this);
    }
    eventTargets.length = 0;
    这个是我在引擎看到的代码,这里是在退出场景的时候,销毁节点时自动调用的,我的理解是为了清楚之前注册的监听。
    我在这个节点注册了两个监听,是不同的对象的,所以这里的eventTargets.length为2,也循环了两遍。
    但是在target.targetOff(this);的时候,我看了那边的代码:
    proto.targetOff = function (target) {
    this.removeAll(target);

    if (target && target.__eventTargets) {
    fastRemove(target.__eventTargets, this);
    }
    };
    这里其实已经对列表进行的删除
    当第二次进入循环的时候i=1,但是eventTargets数组已经只有下标0的数据,所以循环退出
    这个时候eventTargets.length 还剩下1,所以其实是没删干净的。
    我知道在调用on的时候最好同步在不需要的时候调用off,这两个保证一对出现。
    但是在我看到引擎在节点销毁时有做了这一步后,我很多都省略了off

目前这个只有在同一个节点,注册了多个节点的监听后才会有问题,只注册一个或者都是同个节点的是不会有问题的。

不知道新版本有没有这个问题,我现在已经准备对项目on的地方都加上off了,不依赖销毁节点自动off了

我的项目事件管理器是基于cc.EventTarget写的,游戏的驱动也都是基于事件机制来的,所以项目非常多地方都有事件监听。
这个问题其实在之前的几个项目就有出现,但因为之前严格执行on和off一对出现。所以要触发这种情况就漏了写off。补一下就好了。但新项目因为没有严格执行,转向依赖节点销毁时自动off,所以导致需要重新检测的地方非常多,都要去补一遍。

_onPreDestroy () {
        // Schedules
        this.unscheduleAllCallbacks();

        // Remove all listeners
        var eventTargets = this.__eventTargets;
        for (var i = 0, l = eventTargets.length; i < l; ++i) {
            var target = eventTargets[i];
            target && target.targetOff(this);
        }
        eventTargets.length = 0;

        //
        if (CC_EDITOR && !CC_TEST) {
            _Scene.AssetsWatcher.stop(this);
        }

        // onDestroy
        cc.director._nodeActivator.destroyComp(this);

        // do remove component
        this.node._removeComponent(this);
    },
 eventTargets.length = 0; 

这句不就是清空了。

确实是清空了没错
但是问题是漏了一项 target && target.targetOff(this);
导致我的监听没off了,下次我再派发事件就处发了监听,但是节点已经给销毁了

麻烦看一下,是我使用的方式有问题,还是确实这里的写法有漏洞。我现在不敢依靠这个在节点销毁时进行清楚监听了。

麻烦你给个demo吧,这样处理问题直观点。

targetOffDemo.zip (243.6 KB)

做了一个demo
先点击发事件1和发事件2,是能收到事件的
点击删除node后
再次点击事件1和事件2,在点击事件2的时候,就会报错了,删除的node还是监听到了事件,这个时候node已经销毁,出现报错

麻烦大大看下上面的demo,是否有问题

嗯,复现问题了,谢谢反馈,我们会尽快处理。

这是修复PR,能处理这个问题:
https://github.com/cocos-creator/engine/pull/5985

对 倒着循环是正确的做法 但是我有个问题 为啥很多引擎很多代码当中 都是正的 导致有1半的无法被成功释放


去年引擎当中 停止所有动作也是一样的 跟这个bug一样 都是因为不规范的操作引起的 如果有空闲时间 我建议多检查检查 估计还有其他地方也一样

谢谢大佬,我自己也修改了循环保证完全删除了