【问题标题】:Why use named function expressions?为什么使用命名函数表达式?
【发布时间】:2021-04-26 01:11:44
【问题描述】:

我们有两种不同的方式在 JavaScript 中进行函数表达式:

命名函数表达式 (NFE)

var boo = function boo () {
  alert(1);
};

匿名函数表达式

var boo = function () {
  alert(1);
};

它们都可以用boo(); 调用。我真的不明白为什么/何时应该使用匿名函数以及何时应该使用命名函数表达式。它们之间有什么区别?

【问题讨论】:

标签: javascript function anonymous-function function-expression


【解决方案1】:

您应该始终使用命名函数表达式,这就是为什么:

  1. 当需要递归时,可以使用该函数的名称。

  2. 匿名函数在调试时没有帮助,因为您看不到导致问题的函数的名称。

  3. 当你不命名一个函数时,以后更难理解它在做什么。给它起个名字更容易理解。

var foo = function bar() {
    //some code...
};

foo();
bar(); // Error!

例如,在这里,因为名称 bar 是在函数表达式中使用的,所以它不会在外部范围内声明。对于命名函数表达式,函数表达式的名称包含在它自己的范围内。

【讨论】:

    【解决方案2】:

    在匿名函数表达式的情况下,该函数是 anonymous - 从字面上看,它没有名称。您分配给它的变量有一个名称,但函数没有。 (更新:在 ES5 中确实如此。从 ES2015 [又名 ES6] 开始,使用匿名表达式创建的函数通常会得到一个真实的名称 [但不是自动标识符],请继续阅读......)

    名字很有用。名称可以在堆栈跟踪、调用堆栈、断点列表等中看到。名称是一件好事™。

    (您曾经不得不提防旧版本 IE [IE8 及以下] 中的命名函数表达式,因为它们错误地在两个完全不同的时间创建了两个完全独立的函数对象 [更多信息请参阅我的博客文章 Double take]。如果你需要支持 IE8 [!!],最好还是坚持匿名函数表达式或函数声明,但避免命名函数表达式。)

    命名函数表达式的一个关键是它为函数体内的函数创建了一个具有该名称的范围内标识符:

    var x = function example() {
        console.log(typeof example); // "function"
    };
    x();
    console.log(typeof example);     // "undefined"

    不过,从 ES2015 开始,许多“匿名”函数表达式创建带有名称的函数,而这早于各种现代 JavaScript 引擎,它们非常聪明地从上下文推断名称。在 ES2015 中,您的匿名函数表达式会生成一个名为 boo 的函数。但是,即使使用 ES2015+ 语义,也不会创建自动标识符:

    var obj = {
        x: function() {
           console.log(typeof x);   // "undefined"
           console.log(obj.x.name); // "x"
        },
        y: function y() {
           console.log(typeof y);   // "function"
           console.log(obj.y.name); // "y"
        }
    };
    obj.x();
    obj.y();

    函数名称的分配是通过规范中各种操作中使用的SetFunctionName抽象操作完成的。

    简短的版本基本上是任何时候匿名函数表达式出现在赋值或初始化之类的右侧,例如:

    var boo = function() { /*...*/ };
    

    (或者可以是letconst 而不是var,或者

    var obj = {
        boo: function() { /*...*/ }
    };
    

    doSomething({
        boo: function() { /*...*/ }
    });
    

    (最后两个实际上是同一件事),生成的函数将有一个名称(在示例中为boo)。

    有一个重要且有意的例外:分配给现有对象的属性:

    obj.boo = function() { /*...*/ }; // <== Does not get a name
    

    这是因为在添加新功能的过程中出现了信息泄露问题;我对另一个问题here的回答中的详细信息。

    【讨论】:

    • 值得注意的是,至少有两个地方使用 NFE 仍然具有具体的优势:首先,对于旨在通过 new 运算符用作构造函数的函数(给所有这些函数命名会使.constructor 属性在调试过程中更有用,用于弄清楚某个对象到底是什么的实例),以及直接传递给函数而不首先分配给属性或变量的函数文字(例如setTimeout(function () {/*do stuff*/});)。甚至 Chrome 也将它们显示为 (anonymous function),除非您通过命名它们来帮助它。
    • @MarkAmery: “这仍然是真的吗?我...尝试 CTRL-F 来获取这些规则,但找不到它们” 哦,是的。 :-) 它散布在整个规范中,而不是在一个地方定义一组规则,只需搜索“setFunctionName”。我在上面添加了一小部分链接,但它目前显示在大约 29 个不同的地方。如果您的setTimeout 示例没有从为setTimeout 声明的正式参数中获取名称(如果有的话),我只会感到有点惊讶。 :-) 但是是的,如果您知道自己不会处理对它们进行哈希处理的旧浏览器,那么 NFE 绝对有用。
    【解决方案3】:

    如果将函数指定为函数表达式,则可以为其命名。

    它只会在函数内部可用(IE8-除外)。

    var f = function sayHi(name) {
      alert( sayHi ); // Inside the function you can see the function code
    };
    
    alert( sayHi ); // (Error: undefined variable 'sayHi')
    

    此名称用于可靠的递归函数调用,即使它被写入另一个变量。

    此外,可以使用Object.defineProperty(...) 方法覆盖 NFE(命名函数表达式)名称,如下所示:

    var test = function sayHi(name) {
      Object.defineProperty(test, 'name', { value: 'foo', configurable: true });
      alert( test.name ); // foo
    };
    
    test();
    

    注意:使用函数声明无法做到这一点。此“特殊”内部函数名称仅在函数表达式语法中指定。

    【讨论】:

      【解决方案4】:

      如果函数需要引用自身(例如递归调用),命名函数很有用。实际上,如果您将文字函数表达式作为参数直接传递给另一个函数,则该函数表达式不能在 ES5 严格模式下直接引用自身,除非它被命名。

      例如,考虑以下代码:

      setTimeout(function sayMoo() {
          alert('MOO');
          setTimeout(sayMoo, 1000);
      }, 1000);
      

      如果传递给setTimeout 的函数表达式是匿名的,就不可能如此干净地编写这段代码;我们需要在setTimeout 调用之前将其分配给一个变量。这种方式,使用命名函数表达式,稍微短一些,更整洁。

      通过利用arguments.callee...

      setTimeout(function () {
          alert('MOO');
          setTimeout(arguments.callee, 1000);
      }, 1000);
      

      ... 但是arguments.callee 已被弃用,并且在 ES5 严格模式下完全被禁止。因此 MDN 建议:

      避免使用arguments.callee(),方法是为函数表达式命名或在函数必须调用自身的地方使用函数声明。

      (强调我的)

      【讨论】:

        【解决方案5】:

        使用命名函数表达式更好,当您希望能够引用相关函数而不必依赖已弃用的功能(例如 arguments.callee)。

        【讨论】:

        • 这更像是一个评论而不是一个答案。也许详细说明会是有益的
        猜你喜欢
        • 1970-01-01
        • 2013-02-14
        • 1970-01-01
        • 2012-05-16
        • 2012-12-31
        • 2019-10-09
        • 2020-11-22
        相关资源
        最近更新 更多