10

In the online REPL of Babel JS (http://babeljs.io/repl/), when I type in :

let a = (x) => x+1

It will be transpiled to:

"use strict";

var a = function a(x) {
  return x + 1;
};

Here the var a = function a(x) looks a bit confusing to me, because either var a = function(x) or function a(x) is enough as I understand.

Does anyone have ideas about when and why it is necessary to assign a named function to a variable?

Hanfei Sun
  • 39,245
  • 33
  • 107
  • 208
  • 2
    It's not neccesary, but if the function doesn't have a name, it won't show up as 'function a' in stack traces in some browsers. So named functions can be beneficial for easing debugging. Else they'll show up like 'anonymous function'. – Shilly Nov 10 '15 at 09:49
  • @Shilly there's another difference - a named function exposes its name to its own scope. – Alnitak Nov 10 '15 at 09:51
  • @Alnitak: Wrong. It exposes its name to the scope (and with hoisting) only when it's a function definition and not a function expression. Here is a (named) function expression, and it will be accessible only through the variable (which has the same name in this case). But consider the case the var is called `x`, you would call the function `a` with `x()` because `a()` is undefined Basically the function definition on its own line `function lol(){}` would be identical to `var lol = function lol(){}` – Zorgatone Nov 10 '15 at 09:59
  • @Zorgatone I think we're in violent agreement - I don't see anything you've written that disagrees with my assertion that a named function's name is available within the function's own scope. – Alnitak Nov 10 '15 at 10:02
  • It's different, because in the example we have a function expression, which is different from a function definition, and doesn't get hoisted. In the example doesn't really make a big difference because the variable and function have the same name, but if they were called differently, you wouldn't be able to call the named function through its name. Because the named function wouldn't be hoisted and would not be available in the scope – Zorgatone Nov 10 '15 at 10:07
  • @Zorgatone yes, we're still in agreement. I didn't say that this was the _only_ difference, as you can see in my answer. However it can be an important run-time difference. – Alnitak Nov 10 '15 at 10:09
  • I just thought you were talking about the named function in the example (which would be wrong) instead of the named functions in general. I just wanted to point it out for the OP and the readers there not all the named functions (ie. expressions vs declarations) would be hoisted (and then available in the scope) Peace bro =] – Zorgatone Nov 10 '15 at 10:12
  • @Zorgatone TBH, there's way more problems with the other answer ;-) – Alnitak Nov 10 '15 at 10:16
  • @Zorgatone _"if they were called differently, you wouldn't be able to call the named function through its name"_ - Yes, you would, but only within the function. – a better oliver Nov 10 '15 at 13:50
  • 2
    I feel there is too much noise in the comments and the answers. So here is the tl;dr: 1) Arrow functions are expressions, not declarations, hence we need to use a function expression. 2) In ES6, the function name is sometimes inferred from the variable it is assigned to, hence a *named* function expression is used. Ideally one could just assign to `a.name = 'a'`, but not all browsers support this yet. – Felix Kling Nov 10 '15 at 16:29

3 Answers3

6

There are really two different questions here:

  1. What are the differences between the different ways of defining or expressing functions?
  2. Why does let a = (x) => x + 1 get transpiled this way?

In order to answer (2) we need to understand (1)-- which has been extensively discussed on SO and elsewhere.


Question 1

Let's go through the three alternatives you mentioned:

Function declaration:

function a(x) { ... }

Syntactically, these must always begin with function (reference). They are hoisted at parse time and create a named function in the local scope.

(Anonymous) Function expression:

var a = function (x) { ... }

var a itself will be hoisted at parse time, but it will be undefined until this line is executed at runtime.

Named Function expression:

var a = function a(x) { ... }

Though the syntax makes it looks like an assignment to a function declaration, this is actually just a function expression with a name. I find this confusing, but that's the syntax.

The big difference is between function declarations and function expressions. With a declaration, you can do:

a(1);
function a(x) { return x + 1; }

though attempting this with a function expression (named or anonymous) will cause an error.


Question 2

  1. Why does let a = (x) => x + 1 get transpiled this way?

We're assigning the arrow function (x) => x + 1 to a block-scoped variable with let, so we should expect that a is not defined until after this line has been executed at runtime. This should be a function expression, not a function declaration.

Last, why is let a = (x) => x + 1 transpiled to a named function expression rather than a anonymous function expression? What's the difference? As Alnitak and others have pointed out:

  • Function names appear in debuggers, which can be helpful.
  • The scope inside of a named function definition has a reference to the function itself. This allows for recursion and accessing properties of the containing function.

So named function expressions have some nice properties that anonymous function expressions don't. But there actually seems to be disagreement on what should happen here. According to MDN:

Arrow functions are always anonymous

whereas this answer to Why use named function expressions? says:

"[As of ES6] a lot of "anonymous" function expressions create functions with names, and this was predated by various modern JavaScript engines being quite smart about inferring names from context... This is strewn throughout the spec"

Other references:

I've found that the best way to get a handle on this is playing around with the Babel REPL.

Max Wallace
  • 3,185
  • 26
  • 41
  • I can't see the distinction you're making - the only difference in your proposed Babel output is the missing function name on the function expression, and that makes no difference in the _outer_ scope - it only makes a difference _within_ the functions scope. – Alnitak Nov 10 '15 at 09:57
  • Ah, I see where you're going with this - it was confused by your use of the word `If` since what you've described is (almost) what Babel actually does. More relevant is that _If_ Babel used a function definition instead of a function expression then the code you've quoted wouldn't produce an error, when in fact it should. – Alnitak Nov 10 '15 at 10:05
  • I'd also argue that it's not doing it this way to be "safe" - converting an ES2015 lambda into a function expression is simply the *right* thing to do. – Alnitak Nov 10 '15 at 10:10
  • oh, and hoisting happens at _parse_ time, not execution time. – Alnitak Nov 10 '15 at 10:17
  • I think transpiling into `var a = function a(){}` will also cause an error, see this JsFiddle( http://jsfiddle.net/9abuc116/ ) – Hanfei Sun Nov 10 '15 at 10:28
  • @hanfeisun it's not the transpiling that's the error - it's the call to `a(2)` that's made _before_ the assignement to `a` – Alnitak Nov 10 '15 at 11:03
  • this is still wrong - your first code contains a function _expression_ and these are _not_ hoisted. The `var a` is, so `a` is a legal variable, but the assignment of the function expression to `a` doesn't happen until later. – Alnitak Nov 10 '15 at 11:05
  • @Alnitak I've rewritten the entire answer so your comment no longer applies. But let me know if you still see any issues! – Max Wallace Nov 10 '15 at 11:30
  • @hanfeisun You're correct-- my earlier answer was wrong. I've entirely rewritten it. I've also tried to dig up some info on the 'why' behind the question. Please let me know if this is helpful. – Max Wallace Nov 10 '15 at 11:41
  • You didn't answer (2). – a better oliver Nov 10 '15 at 14:10
  • @zeroflagL I believe I did. What more are you looking for? If you think the answer can be improved, please edit it or add your own. – Max Wallace Nov 10 '15 at 14:41
  • I reread your answer carefully and the last quote eventually contains some relevant information. So I was distracted by all the not so relevant information before ;) – a better oliver Nov 10 '15 at 15:56
4

If you write:

function a(x) { }

then the function is hoisted to the top of the enclosing scope, and a becomes available immediately at parse time within the entire scope.

However, when you write:

var a = function a(x) { }

then var a will not have a defined value in the enclosing scope until this line is actually executed.

However, within that function, a different a will exist as a locally scoped reference to the function itself.

By using the let a = function ... construct Babel is being more consistent with the latter form, ensuring that a is assigned at run time to a (named) function expression instead of using a parse time function declaration.

Alnitak
  • 313,276
  • 69
  • 379
  • 466
2

It appears that this is according to standard (12.14.4):

AssignmentExpression[In, Yield] : LeftHandSideExpression[?Yield] = AssignmentExpression[?In, ?Yield]

1.If LeftHandSideExpression is neither an ObjectLiteral nor an ArrayLiteral, then
  a. Let lref be the result of evaluating LeftHandSideExpression.
  b. ReturnIfAbrupt(lref).
  c. Let rref be the result of evaluating AssignmentExpression.
  d. Let rval be GetValue(rref).
  e. If IsAnonymousFunctionDefinition(AssignmentExpression) and IsIdentifierRef of LeftHandSideExpression are both true, then
    I.   Let hasNameProperty be HasOwnProperty(rval, "name").
    II.  ReturnIfAbrupt(hasNameProperty).
    III. If hasNameProperty is false, perform SetFunctionName(rval, GetReferencedName(lref)).

So, whenever an assignment of an unnamed function expressions to a named identifier is evaluated, the function name should be set to the identifer name.

Babel follows this process, and generates a compatible ES5 implementation.
Chrome (v46.0.2490.71, V8 engine...), doesn't follow this process, and name equals '' in that cases.


As for the question itself...

In Javascript, when is it necessary to assign a named function to a variable?

The answer is never. It's up to the developer to decide if / when to use a named function. The decision boils down to specific need for a name (such as when "stringifying" a function), or debug needs (better stack traces...).

Community
  • 1
  • 1
Amit
  • 41,690
  • 8
  • 66
  • 101
  • I disagree with _"never"_. What about recursion? – a better oliver Nov 10 '15 at 14:04
  • @zeroflagL - can't post code in comments but... [jsfiddle](http://jsfiddle.net/h8qtfqdr/). – Amit Nov 10 '15 at 14:30
  • 1
    Your example is wrong actually, because the return value of an iefe (whatever the value is) doesn't count as an `anonymousFunctionDefinition`. Babel does implement this correctly and completely. – Bergi Nov 10 '15 at 14:34
  • @Bergi - Interesting. I misunderstood *IsAnonymousFunctionDefinition(AssignmentExpression)*, but you're right. *AssignmentExpression* is **parsed**, but not **evaluated**. Thanks! – Amit Nov 10 '15 at 15:21
  • That example works, of course. But I could assign the function to another variable and assign a new value to `a`. Then it breaks, if the function doesn't have a name to call itself. – a better oliver Nov 10 '15 at 15:24
  • @zeroflagL - Yeah, that's true. contrived, but true :-). That doesn't change my opinion on "*never*" though. It's not *necessary to assign a named function to a variable*, even for recursion purposes. – Amit Nov 10 '15 at 15:32