命名函数表达式笨拙的作用域

206 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

JavaScript 函数无论放在何处看起来似乎都是一样的,但是根据上下文其含义会发生变化

查看一下代码片段

 function test(x){return x}

这段代码可以是一个函数声明,也可以是命名函数表达式,这取决于它出现的地方。它定义一个函数并且绑定到当前作用域的一个变量。在程序的最顶层,以上声明将创建一个名为 test 的全局函数。但是同一段代码也可以作为一个表达式,它可以有截然不同的含义。例如 :

 var result = function test(x){return x}

根据 ECMAScript 规范,上面那一段代码将函数 test 绑定到了变量 result 而不是变量 test。当然,函数的表达式命名并不是必要的。我们可以省略

 var result = function (x){return x}

匿名和命名函数表达式的官方区别在于命名函数会绑定到与其函数名相同的变量上,该变量将作为该函数内的一个局部变量。这也是递归函数的原理

 // 求个阶乘呗
 var result = function fn(value) {
   if (value == 1) {
     return value;
   } else {
       // fn 变量成为了函数内的一个局部变量
     return value * fn(value - 1);
   }
 };
 console.log(result(5));

需要注意的是变量 fn 的作用域只在其自身函数中。不像函数声明,命名函数表达式不能通过其内部的函数名在外部被引用。

使用外部作用域的函数名也可以达到同样的效果

 // 求个阶乘呗
 function fn(value) {
   if (value == 1) {
     return value;
   } else {
     return value * fn(value - 1);
   }
 }
 console.log(fn(5));

:::tip

命名函数表达式真正的用处是进行调试。大多数现代的 JavaScript 环境都提供对 Error 对象的栈跟踪功能。在栈跟踪中,函数表达式的名称通常作为其入口使用。用于检查栈的设备调试器对命名函数表达式有类似的使用。

:::

总结

  • 在Eror对象和调试器中使用命名函数表达式改进栈跟踪。
  • 谨记在错误百出的 JavaScript环境中会提升命名函数表达式声明,并导致命名函数表达式的重复存储。
  • 考虑避免使用命名函数表达式或在发布前删除函数名。