【问题标题】:How does naming an anonymous function in JavaScript make a difference?在 JavaScript 中命名匿名函数有何不同?
【发布时间】:2012-04-25 15:33:45
【问题描述】:

我正在分析来自 John Resig 网站的以下两个 url,但我不明白为匿名函数命名有何不同。

我的理解是,匿名函数的名称只能在函数定义内部使用,不能在函数定义之外使用,但在以下链接中会产生巨大的差异

任何解释或参考都会有很大帮助。

我仍然对 #14 中的以下几行感到困惑

var samurai = { yell: ninja.yell }; 
var ninja = {};
assert( samurai.yell(4) == "hiyaaaa", "The method correctly calls itself." ); 

当 ninja 现在指向一个空白对象时,Samurai.yell 方法如何仍然能够指向 ninja.yell。

#13 和#14 之间的唯一区别是为#14 中的函数表达式提供名称。

ninja.yell 是否被复制到大喊而不被引用,或者这些 NAMED 函数表达式在某些场景中具有全局范围?

在 #13 和 #14 中发生了同样的事情,唯一的区别是函数在 #14 中命名而在 #13 中未命名,加上 #14 中的 ninja = {} 和 #13 中的 ninja = null。是否有任何关于命名函数表达式的隐藏概念我错过了,这使得 #14 可行而 #13 不可行。

【问题讨论】:

    标签: javascript


    【解决方案1】:

    在内部示例中,您可以跳过 #13 中对 ninja 对象的额外访问

    匿名闭包(需要访问对象ninja,尽管我们已经在那个上下文中):

    var ninja = { 
      yell: function(n){ 
        return n > 0 ? ninja.yell(n-1) + "a" : "hiy"; 
      } 
    };
    

    命名闭包可以直接调用:

    var ninja = { 
      yell: function yell(n){ 
        return n > 0 ? yell(n-1) + "a" : "hiy"; 
      } 
    };
    

    另一个优点是命名闭包启用堆栈跟踪:

    所以假设你这样做:

    (function fooBar() { console.log(brazl); })();
    // will create an error with "fooBar" in the stack trace instead of "anonymous function"
    

    编辑:虽然它有时看起来像开销,但它有助于在开发过程中进行调试,例如 YUICompressor 和 Closure Compiler 可以在本质上不需要它们时去除这些名称

    【讨论】:

      【解决方案2】:

      不想与 Kolink 好斗,但他说这不是一个好例子有点过分了。 #14 与(在您共享的链接中)有关的是命名函数表达式(与函数声明不同的动物)。无论函数引用传递到哪里,如果你命名你的函数表达式,它总是有一种方法来调用它自己,从它自己内部。这个名字,你给你的函数表达式,是一个只有它知道的名字;它不存在于任何外部范围内。

      有关函数表达式与函数声明的进一步讨论,请参阅 MDN 上的 herehere。底部的第二个链接有一个关于命名函数表达式的标题。它确实有用;有关一次性递归函数的示例,请参阅 my Gist,它不会向局部或全局变量范围添加任何内容(例如,对于一次性 DOM 遍历很有用)。

      此外,Tobias(在他的回答中)指出了命名函数表达式的其他良好用途,即在调试中。

      【讨论】:

      • 感谢您提供更准确的答案,但我仍然对 #14 感到困惑 - 当 ninja 现在指向空白对象时,Samurai.yell 方法如何仍然能够指向 ninja.yell。
      • 好的,它是这样工作的:当对象通过对象字面量组合时,匿名函数被分配给ninja.yell。然后将ninja.yell中的函数对象(因为所有JS函数都是一等对象)赋值给samurai.yellninja 然后被擦干净:它被分配了一个空白的新对象。但是samurai.yell 仍然持有对原始函数对象的引用,因此它不会被垃圾回收;它继续存在。只要对任何对象的一个​​引用仍然存在,就不会发生垃圾收集并且对象会继续存在。有意义吗?
      • 感谢 Paul 带来了我在 #14 中无法理解的垃圾收集点,但在 #13 中仍然发生同样的事情,但它不起作用。唯一的区别是表达式在#14 中命名,在#13 中未命名,加上#13 中的 ninja=null 和#14 中的 ninja={}。是否还有任何关于命名函数表达式的隐藏概念我遗漏了,这使得 #14 可行但 #13 不可行?
      • 好的,我更彻底地阅读了您的观点,并再次分析了#13 和#14。明白了#13 和#14 之间还有一个区别,#13 我们将yell 称为ninja.yell,而#14 它只是yyy,正如您在垃圾收集点中所阐明的那样。现在很清楚,我接受你的回答是正确的。非常感谢保罗 :)
      • 保罗回答得很好。但看起来即使没有命名函数,它仍然可以通过arguments.callee 调用自己,如 Resig 的下一个示例 (#15) 所示。即使将ninja 设置为null,如果通过arguments.callee 发生递归,则通过samurai 现在引用的匿名函数的递归也可以完美地工作。我的观察和结论是否正确,还是我遗漏了什么?谢谢。
      【解决方案3】:

      在第一种情况下,yell 方法试图访问ninja.yell,而不管是哪个对象调用它。而在第二个中,它会尝试调用存在的yell,因为该函数已命名。

      这不是一个很好的例子。一个很好的例子是使用this.yell 而不是ninja.yell,从而从当前对象中获取yell 方法。

      【讨论】:

      • 不仅仅是函数的命名;它是一个命名函数表达式,而不仅仅是一个函数声明(必须有一个名称)。区别不仅仅是语义上的。鉴于可以通过callapply 注入作用域,并且删除了对函数的原始引用,因此函数具有始终引用自身的方式,这使得命名函数表达式非常有用,恕我直言。
      【解决方案4】:

      网站http://kangax.github.com/nfe/ 是一个很好的参考。是的,就它是一个函数表达式而言,该名称仅在内部可用(例如,用于递归调用,如演示中所示)并且还有助于调试(例如在堆栈跟踪中),因为它设置了 name 的属性函数。

      【讨论】:

        猜你喜欢
        • 2022-08-02
        • 2013-05-27
        • 1970-01-01
        • 2011-04-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-09-03
        • 1970-01-01
        相关资源
        最近更新 更多