为什么 Airbnb 风格指南说不鼓励依赖函数名称推断?

Why does the Airbnb style guide say that relying on function name inference is discouraged?

// 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 风格指南。有人可以解释为什么 "relying on function name inference is discouraged" 吗?这只是风格问题吗?

这是因为:

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

推断名称为 Listing,虽然看起来是您命名的,但实际上不是:

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
}

我认为这也可能与您可能 运行 的意外行为有关,即隐式地为您可能期望的匿名函数提供词法名称。

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

(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

您可以查看 'view compiled' 这个快速 DEMO 以了解 Babel 实际上是如何转换它以保持匿名函数的行为。


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

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

编辑 #2:在他们的 Javascript style guide

中找到了 AirBnbs 的原因

Don’t forget to name the expression - anonymous functions can make it harder to locate the problem in an Error's call stack (Discussion)

原回答如下

MDN 对 function name inference 的工作原理有很好的 运行-down,包括两个警告:

观察

在以下两种情况下存在非标准<function>.name推理行为:

  1. 使用脚本解释器时

The script interpreter will set a function's name property only if a function does not have an own property called name...

  1. 使用js工具时

Be careful when using Function.name and source code transformations such as those carried out by JavaScript compressors (minifiers) or obfuscators

....

In the uncompressed version the program runs into the truthy-branch and logs 'foo' is an instance of 'Foo' whereas in the compressed version it behaves differently and runs into the else-branch. Therefore, if you rely on Function.name like in the example above, make sure your build pipeline doesn't change function names or don't assume a function to have a particular name.

什么是函数名推断?

The name property returns the name of a function, or (before ES6 implementations) an empty string for anonymous functions

function doSomething() {}

console.log(doSomething.name); // logs "doSomething"

Functions created with the syntax new Function(...) or just Function(...) have their name property set to an empty string. In the following examples anonymous functions are created, so name returns an empty string

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
}

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

  • 通知reader没有this,没有arguments
  • 看起来更好(恕我直言)
  • 性能(我上次查看时,箭头函数略快)

编辑:内存快照

我听 Podcast 客人说他必须处理使用带内存分析的箭头函数的限制,我一直在 完全相同之前的情况。

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

另外,我只使用过一次内存快照,所以我很乐意默认放弃一些 "instrumention" 为了(主观)清晰度。

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

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

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

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

var Foo = function Foo() {};

并且 TypeScript 违反了规范:

var Foo = function () {};

这并不意味着命名函数表达式不好并且应该被劝阻。只要转译器符合规范或函数名称无关紧要,就可以消除这种担忧。

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