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

IT技术 javascript reactjs ecmascript-6 eslint airbnb-js-styleguide
2021-04-01 17:16:11
// 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 风格指南。有人可以解释为什么“不鼓励依赖函数名称推断”吗?这只是风格问题吗?

4个回答

我认为这也可能与您可能遇到的意外行为有关,因为您可能会因隐式地将词法名称赋予您可能期望的匿名函数而遇到这种行为。

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

(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

你可以在这个快速演示中检查“编译视图” ,看看 Babel 如何实际转换它以维护匿名函数的行为。


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

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

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

编辑 #2:在他们的Javascript 风格指南中找到 AirBnbs 原因

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

原答案如下

MDN 对函数名称推断的工作原理有很好的总结,包括两个警告:

观察

<function>.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
  • 看起来更好(恕我直言)
  • 性能(上次我看,箭头函数稍微快一点)

编辑:内存快照

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

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

另外,我只使用过一次内存快照,所以我觉得默认情况下放弃一些(主观)清晰度的“工具”很舒服。

这是因为:

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

具有 Listing 的推断名称,虽然看起来您正在命名它,但实际上您不是:

例子

// 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属性等于""
2021-06-04 17:16:11
好吧,我打开了垃圾箱中的 babel,Brain 单击我发布的示例链接,应该没问题...
2021-06-04 17:16:11
你自己的例子不是表明所有这些函数实际上都是命名的吗?
2021-06-14 17:16:11
@Brian:那是因为它还没有实现所有的 ES6
2021-06-16 17:16:11
我应该更清楚,这const bar是重点,在那个例子baz中优先。
2021-06-17 17:16:11

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

函数name属性不应该用于客户端应用程序中的调试,因为函数原始名称在缩小过程中丢失。至于调试,如果函数在调用堆栈中没有有意义的名称,则效率会降低,因此在某些情况下保留它是有益的。

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

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

var Foo = function Foo() {};

TypeScript 打破了规范:

var Foo = function () {};

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

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