【问题标题】:Anonymous functions as event handlers in Action Script - good or bad?Action Script 中作为事件处理程序的匿名函数 - 好还是坏?
【发布时间】:2010-12-12 09:45:58
【问题描述】:

我从 JS 世界来到 AS3,我应该承认匿名函数是我的弱点。我倾向于在任何地方使用它们。现在,来到 AS3,我在很多地方听到和读到,AS 和 Flash 在处理垃圾收集方面非常糟糕,应该手动清空、处理和删除所有事件处理程序和对象,以避免奇怪和无法解释的内存泄漏和崩溃。不确定这其中的哪一部分是正确的,但我想从一开始就遵循最佳做法。

所以我的问题是 - 使用匿名函数作为事件处理程序的想法有多糟糕?例如,考虑这样的代码:

addEventListener(Event.ENTER_FRAME, function() : void {
    controls.elapsed = stream.time;
});

contorls.elapsed 是设置器,除了设置视频播放器的当前播放时间外,还会更新整个 UI,streamNetStream 对象,流式传输实际视频。

还有很多其他地方可以使用匿名函数使代码更简洁、更直观。检查以下代码以获得控制栏的简单淡入效果:

public function showControls() : void
    {
        var self:Controls = this;

        if (!visible) {
            visible = true;
            fadeTimer = new Timer(30, 10);
            fadeTimer.addEventListener(TimerEvent.TIMER, function() : void {
                self.alpha += 0.1;
            });
            fadeTimer.addEventListener(TimerEvent.TIMER_COMPLETE, function() : void {
                self.alpha = 1;
            });
            fadeTimer.start();
        }
    }

我非常喜欢它的外观并适合代码,但我担心泄漏。虽然 Event.ENTER_FRAME 处理程序可能永远不会以这种形式变得有害,但计时器侦听器呢。我应该手动删除这些侦听器,还是一旦我设置 fadeTimer = null 就会自动删除它们?是否可以完全删除带有匿名函数的侦听器?

【问题讨论】:

  • 我不喜欢匿名函数。一方面,它们很丑,你也不能在其他地方重用该函数(可能导致重复代码),最后,它们创建了一个对性能和内存有一些影响的激活对象。 onflex.org/ACDS/AS3TuningInsideAVM2JIT.pdf
  • AS 中有什么东西不会对性能和内存造成影响吗? :) 好吧,在我的理解中,匿名函数只有在不需要重用这些函数、不想弄乱类定义并希望将整个逻辑保存在一个地方时才有用。这就是我倾向于使用它们的方式和时间。感谢您的链接,我会检查它。
  • 嗯......“一些性能和内存影响”实际上是一个引用。现在是什么样的声明? “一些”是什么意思?如果按照它的意思来解读,那么“一些”听起来可以忽略不计。或没有?多么模糊。
  • 似乎“激活对象”并非特定于 AS,而是 ECMA 标准的一部分。并且也为 JS 中的匿名函数创建。但这并不能阻止网络上的每个人使用它,对吧?或者,该功能的 AS 实现可能是错误且缓慢的?

标签: actionscript-3 event-handling anonymous-function


【解决方案1】:

刚刚注意到这篇文章 - 有几件事可能对您有用。一个是 arguments.callee(它是对您所在的当前函数的引用)。这对于删除匿名函数中的引用很有用。此外,您可以在 addEventListener 代码中使用弱引用——但是,这不适用于匿名变量,因为它们几乎会立即被 GC。为简单起见,我像这样重写了您的代码:(应该可以工作-尚未测试)

private function showControls() : void {

    if (visible) {
        return;
    }

    var self:DisplayObject = this;

    var fadeTimer= new Timer(30,10);
    var handler = function(e:Event) {

        switch (e.type) {

            // timer complete
            case TimerEvent.TIMER_COMPLETE:

                // remove references to this anonymous function -- for garbage collection
                fadeTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, arguments.callee);
                fadeTimer.removeEventListener(TimerEvent.TIMER, arguments.callee);

                // break out
                return self.alpha = 1;

            // timer
            case TimerEvent.TIMER:
                return self.alpha += 0.1;

        }
    }

    fadeTimer.addEventListener(TimerEvent.TIMER, handler);
    fadeTimer.addEventListener(TimerEvent.TIMER_COMPLETE, handler);
    fadeTimer.start();

}

【讨论】:

  • 哇,太棒了!谢谢,令人惊讶的是我从来没有想过以这种方式使用被调用者......但如果它有效,我认为它应该是一种非常流行的做法,对吧?它真的有用吗,你测试过吗?有什么隐藏的警告吗?
  • 隐藏的警告是不推荐。几天前我做了一个测试,效果很好——尽管我仍然不认为它是“伟大”的选择。我认为班级成员是最好的方式。您应该查找特征对象——它基本上是一个隐藏的内部对象,允许 Flash 快速查找成员。我通常喜欢将一堆处理程序与 switch 事件混为一谈,以使事件处理更容易一些。这种代码只是要求创建一个实用程序类。
【解决方案2】:

我会这样做。而且,当您想确保在中断时清除计时器时,请务必使用 dispose()。

private function showControls() : void
{
    if(_isVisible)
        return;

    // start you control here
    _fadeTimer = new Timer(30, 10);
    _fadeTimer.removeEventListener(TimerEvent.TIMER, updateFade);
    _fadeTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, updateFadeComplete);
    _fadeTimer.start();
}

private function updateFade(event : TimerEvent) : void
{
    // update fade here
}

private function updateFadeComplete(event : TimerEvent) : void
{
    dispose();
}


private function dispose() : void
{
    if(_fadeTimer)
    {
        _fadeTimer.stop();
        _fadeTimer.removeEventListener(TimerEvent.TIMER, updateFade);
        _fadeTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, updateFadeComplete);
        _fadeTimer = null;
    }
}

【讨论】:

  • 顺便问一下,为什么这些下划线无处不在?它们有点增加了代码的笨拙,不是吗?
  • 对不起。因为他们是私人班级成员。
  • @jayarjo - 下划线 a 是命名私有字段/变量的常见但不是必需的约定
  • 通常下划线的属性名称应该在有伴随访问器的地方使用。如果一个属性没有访问器,我只写属性名称,不带下划线。
【解决方案3】:

在可行的地方使用函数方法并没有错。就内存泄漏而言,您需要将对象跟踪到舞台以查看是否可以将其删除。

向控件添加ENTER_FRAME 事件处理程序可确保控件 具有对匿名函数 的引用。由于代码是控件的一部分(或者看起来如此),这很好,因为当控件存在时,匿名函数将被删除。

向计时器添加事件处理程序可确保计时器 具有对匿名函数 的引用。如果计时器正在运行,它将使匿名函数引用保持活动状态,并通过关联保持实体控件。但是,一旦计时器停止,它和函数都应该被收集。

如果一切都失败了,请使用分析器看看! ;)

【讨论】:

  • “应该收集”是指自动收集吗?或者我将不得不干扰手动 dispose() 函数? :)
  • 我的意思是垃圾收集应该在执行扫描时自动删除它。
  • 上面的代码,fadetimer 永远不会被垃圾回收。它要么需要明确的 removeEventListener,要么需要将 addEventListener 设置为 useWeakReferences = false。 (即 addEventListener(type, callback, false[usecapture], 0[priority], true[useweakref]); 从舞台上移除控件不会移除事件处理程序。
  • @ansiart - 我不会删除事件处理程序,但它会使持有对它的引用的对象有资格进行垃圾收集(假设它没有植根于其他地方)。
猜你喜欢
  • 2010-11-26
  • 1970-01-01
  • 2018-10-06
  • 2022-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-17
相关资源
最近更新 更多