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

☆樱花仙子☆ 提交于 2019-11-28 04:19:09

I think this could also have something to do with the unexpected behaviour that you might run into from implicitly giving a lexical name to what you may expect to be an anonymous function.

Say for example someone understood the arrow function:

(x) => x+2;

To have the regular function equivalent:

function(x) {
  return x+2;
}

It would be pretty easy to expect this code:

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

To then be the equivalent of:

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

Where the function remains anonymous and would be incapable of referencing itself to do things like recursion.

So if then, in our blissful ignorance, we had something like this happening:

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

It would successfully run because that function obviously wasn't anonymous:

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

This could potentially be exacerbated by the fact that in other situations where Babel implicitly adds a name to anonymous functions, (which I think is actually a bit of a side-effect of supporting implicit function names in the first place, though I could be wrong on that), they correctly handle any edge cases and throw reference errors where you would expect.

For example:

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

You can check 'view compiled' on this quick DEMO to see how Babel is actually transpiling that to maintain the behaviour of an anonymous function.


In short, being explicit with what you are doing is typically a good idea because you know exactly what to expect from your code. Discouraging the use of implicit function naming is likely a stylistic choice in support of this while also remaining concise and straightforward.

And probably hoisting. But hey, fun side trip.

EDIT #2: Found AirBnbs reason in their Javascript style guide

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

Original answer below

MDN has a good run-down on how function name inference works, including two warnings:

Observations

There is non-standard <function>.name inference behaviour in the following two scenarios:

  1. when using script interpreters

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

  1. when using js tooling

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.

What is function name inference?

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

Browsers that implement ES6 functions can infer the name of an anonymous function from its syntactic position. For example:

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

Opinion

Personally I prefer (arrow) functions assigned to a variable for three basic reasons:

Firstly, I don't ever use function.name

Secondly, mixing lexical scope of named functions with assignment feels a little loose:

// 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
}

And thirdly, all things being equal, I prefer arrow functions:

  • communicate to reader that there is no this, no arguments etc
  • looks better (imho)
  • performance (last time I looked, arrow functions were marginally faster)

EDIT: Memory snapshots

I was listening to a Podcast and guest told of a situation were he had to deal with the limitations of using arrow functions with memory profiling, I have been in the exact same situation before.

Currently, memory snapshots will not include a variable name - so you might find yourself converting arrow functions to named functions just to hook up the memory profiler. My experience was quite straightforward, and I'm still happy with arrow functions.

Plus I've only used memory snapshots once, so I feel comfortable forgoing some "instrumention" for (subjective) clarity by default.

This is because:

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

has an inferred name of Listing, while it looks like you are naming it, you actually aren't:

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

what about this?

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

function qux() {
  console.log(qux.name); // qux
}

As any other style guide, Airbnb's is opinionated and isn't always well reasoned.

Function name property isn't supposed to be used for anything but debugging in client-side application because function original name is lost during minification. As for debugging, it becomes less efficient if a function doesn't have a meaningful name in call stack, so it's beneficial to preserve it in some cases.

A function gets name with both function definition like function Foo = () => {} and function named expression like an arrow in const Foo = () => {}. This it results in Foo function having a given name, Foo.name === 'Foo'.

Some transpilers follow the specification. Babel transpiles this code to ES5:

var Foo = function Foo() {};

And TypeScript breaks the specification:

var Foo = function () {};

This doesn't mean that named function expression is bad and should be discouraged. As long as a transpiler is spec-compliant or function name doesn't matter, this concern can be discarded.

The problem is applicable to transpiled applications. It depends on a transpiler in use and a necessity to keep function name property. The problem doesn't exist in native ES6.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!