【问题标题】:Why does the Airbnb style guide say that relying on function name inference is discouraged?为什么 Airbnb 风格指南说不鼓励依赖函数名称推断?
【发布时间】:2016-09-14 07:27:33
【问题描述】:
// bad
class Listing extends React.Component {
  render() {
    return <div>{this.props.hello}</div>;
  }
}

// bad (relying on function name inference is discouraged)
const Listing = ({ hello }) => (
  <div>{hello}</div>
);

// good
function Listing({ hello }) {
  return <div>{hello}</div>;
}

这摘自 Airbnb react 风格指南。有人可以解释为什么“不鼓励依赖函数名称推断”吗?这只是风格问题吗?

【问题讨论】:

标签: javascript reactjs ecmascript-6 eslint airbnb-js-styleguide


【解决方案1】:

与任何其他风格指南一样,Airbnb 是固执己见的,并不总是有充分的理由。

Function name property 除了在客户端应用程序中进行调试外,不应用于任何其他用途,因为在缩小过程中会丢失函数原始名称。至于调试,如果函数在调用堆栈中没有有意义的名称,它的效率就会降低,因此在某些情况下保留它是有益的。

一个函数得到name,函数定义如function Foo = () =&gt; {},函数命名表达式如const Foo = () =&gt; {}中的箭头。这会导致 Foo 函数具有给定名称 Foo.name === 'Foo'

一些转译器遵循规范。 Babel 将这段代码转译为 ES5:

var Foo = function Foo() {};

TypeScript 打破了规范:

var Foo = function () {};

这并不意味着命名函数表达式不好,应该不鼓励。只要转译器符合规范或函数名称无关紧要,就可以忽略此问题。

该问题适用于已转译的应用程序。这取决于使用的转译器和保持name 属性的必要性。原生 ES6 中不存在该问题。

【讨论】:

    【解决方案2】:

    编辑 #2:在他们的 Javascript style guide 中找到 AirBnbs 的原因

    不要忘记为表达式命名 - 匿名函数会使在错误的调用堆栈中定位问题变得更加困难 (Discussion)

    原答案如下

    MDN 对 function name inference 的工作方式有很好的了解,包括两个警告:

    观察

    &lt;function&gt;.name在以下两种情况下存在非标准推理行为:

    1. 使用脚本解释器时

    仅当函数没有自己的名为 name 的属性时,脚本解释器才会设置函数的 name 属性...

    1. 使用 js 工具时

    在使用 Function.name 和源代码转换时要小心,例如由 JavaScript 压缩器(压缩器)或混淆器执行的转换

    ....

    在未压缩的版本中,程序运行到truthy-branch并记录'foo'是'Foo'的一个实例,而在压缩版本中它的行为不同并运行到else-branch。因此,如果您像上面的示例一样依赖 Function.name,请确保您的构建管道不会更改函数名称或不假定函数具有特定名称。

    什么是函数名推断?

    name 属性返回函数的名称,或者(在 ES6 实现之前)匿名函数的空字符串

    function doSomething() {}
    
    console.log(doSomething.name); // logs "doSomething"
    

    使用语法 new Function(...) 或仅 Function(...) 创建的函数的 name 属性设置为空字符串。在以下示例中创建了匿名函数,因此 name 返回一个空字符串

    var f = function() {};
    var object = {
      someMethod: function() {}
    };
    
    console.log(f.name == ''); // true
    console.log(object.someMethod.name == ''); // also true
    

    实现 ES6 函数的浏览器可以从匿名函数的句法位置推断出匿名函数的名称。例如:

    var f = function() {};
    console.log(f.name); // "f"
    

    意见

    出于三个基本原因,我个人更喜欢(箭头)函数分配给变量:

    首先,我从不使用function.name

    其次,将命名函数的词法范围与赋值混合起来感觉有点松散:

    // This...
    function Blah() {
       //...
    }
    Blah.propTypes = {
     thing: PropTypes.string
    }
    // ...is the same as...
    Blah.propTypes = {
     thing: PropTypes.string
    }
    function Blah() {
       //...
    }
    
    // ALTERNATIVELY, here lexical-order is enforced
    const Blah = () => {
       //...
    }
    Blah.propTypes = {
        thing: PropTypes.string
    }
    

    第三,在所有条件相同的情况下,我更喜欢箭头函数:

    • 告诉读者没有this,没有arguments
    • 看起来更好(恕我直言)
    • 性能(我上次查看时,箭头函数稍微快了一点)

    编辑:内存快照

    我正在听一个Podcast 和客人告诉他的情况是他必须处理使用箭头函数和内存分析的限制,我以前也遇到过完全相同的情况。

    目前,内存快照不包含变量名称 - 因此您可能会发现自己将箭头函数转换为命名函数只是为了连接内存分析器。我的经验非常简单,我仍然对箭头函数感到满意。

    另外,我只使用过一次内存快照,所以我很乐意放弃一些默认的(主观)清晰度的“工具”。

    【讨论】:

      【解决方案3】:

      我认为这也可能与您可能遇到的意外行为有关,因为您可能会因为隐含地为您可能期望成为匿名函数的函数提供词法名称而遇到这种情况。

      比如说有人理解箭头函数:

      (x) => x+2;
      

      要具有等效的常规功能:

      function(x) {
        return x+2;
      }
      

      很容易期待这段代码:

      let foo = (x) => x+2;
      

      那么等价于:

      let foo = function(x) {
        return x+2;
      }
      

      函数保持匿名并且无法引用自身来执行递归等操作。

      如果那时,在我们幸福的无知中,我们发生了这样的事情:

      let foo = (x) => (x<2) ? foo(2) : "foo(1)? I should be a reference error";
      console.log(foo(1));
      

      它会成功运行,因为该函数显然不是匿名的:

      let foo = function foo(x) {
        return (x<2) ? foo(2) : "foo(1)? I should be a reference error";
      }  
      

      在其他情况下,Babel 会为匿名函数隐式添加名称,这可能会加剧这种情况(我认为这实际上是首先支持隐式函数名称的副作用,尽管我可能是错误的),他们正确处理任何边缘情况并在您期望的地方抛出参考错误。

      例如:

      let foo = {
        bar: function() {}
      } 
      
      // Will surprisingly transpile to..
      
      var foo = {
        bar: function bar() {}
      }; 
      
      
      // But doing something like:
      
      var foo = {
        bar: function(x) {
          return (x<2) ? bar(2) : 'Whats happening!?';
        }
      }
      
      console.log(foo.bar(1));
      
      // Will correctly cause a ReferenceError: bar is not defined
      

      您可以在此快速 DEMO 上查看“查看编译”,了解 Babel 是如何实际转换它以维护匿名函数的行为。


      简而言之,明确说明您正在做的事情通常是一个好主意,因为您确切地知道对您的代码有什么期望。不鼓励使用隐式函数命名可能是支持这一点的一种风格选择,同时也保持简洁明了。

      而且可能正在吊装。但是,嘿,有趣的旅行。

      【讨论】:

      • Babel(至少目前)不会自动命名这些函数。在第一种情况下,您从刚刚定义的变量中引用函数,在后一种情况下,您不是(您需要在函数中使用foo.bar(2) 而不是bar(2))。这实际上是相反的问题,我们希望它们被命名,但它们不像 Ashley Coolman 在下面写的那样。
      【解决方案4】:

      这是因为:

      const Listing = ({ hello }) => (
        <div>{hello}</div>
      );
      

      有一个推断的名称列表,虽然看起来你正在命名它,但实际上你不是:

      Example

      // we know the first three ways already...
      
      let func1 = function () {};
      console.log(func1.name); // func1
      
      const func2 = function () {};
      console.log(func2.name); // func2
      
      var func3 = function () {};
      console.log(func3.name); // func3
      

      这个呢?

      const bar = function baz() {
          console.log(bar.name); // baz
          console.log(baz.name); // baz
      };
      
      function qux() {
        console.log(qux.name); // qux
      }
      

      【讨论】:

      • 你自己的例子不是表明所有这些函数实际上都被命名了吗?
      • Chrome中func1、func2、func3的name属性等于""
      • 我应该更清楚,const bar 是重点,在该示例中 baz 优先。
      • @Brian: 那是因为它还没有实现所有的 ES6
      • 好吧,我在bin中打开了babel,Brain点击我发布的示例链接,应该没问题...
      猜你喜欢
      • 2016-08-04
      • 2019-08-24
      • 2012-10-03
      • 2015-02-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-05
      • 1970-01-01
      相关资源
      最近更新 更多