20

What are the advantages/disadvantages to create a top level function in ES6/ES2015 in those different ways? Or is this just a matter of taste/style guide etc?

Option 1:

function square(n) {
   return n * n;
}

Option 2:

var square = function(n) {
  return n * n;
};

Option 3:

var square = (n) => {
   return n * n;
};

Option 4:

const square = (n) => {
   return n * n;
};
Kasper
  • 8,678
  • 10
  • 34
  • 53
  • It should either be option 1 or `const square = n => n * n;` – Bergi Jul 20 '16 at 11:55
  • possible duplicate of [`var functionName = function() {}` vs `function functionName() {}`](http://stackoverflow.com/q/336859/1048572), [When should I use Arrow functions in ECMAScript 6?](http://stackoverflow.com/q/22939130/1048572) *and* [Arrow function vs function declaration / expressions: Are they equivalent / exchangeable?](http://stackoverflow.com/q/34361379/1048572) – Bergi Jul 20 '16 at 12:17

2 Answers2

18

Note: I've posted this as a community wiki, we can all add to the list, clarify, etc. Please no opinions. Keep it objective.


Or is this just a matter of taste/style guide etc?

There will be a strong influence of style, yes, but there are some objective observations we can make in terms of the functionality and runtime characteristics of the options that can be used to decide which is appropriate for a given use-case.

Option 1:

function square(n) {
   return n * n;
}
  • Hoisted (because it's a function declaration).
  • Until ES2015 (ES6), only valid at global scope or at the top-level of a function; ES2015+ allows them within control flow statements, but the rules are complex.
  • Can be overwritten later via square = ... (or a later function declaration).
  • Creates an object and assigns it to square.prototype, even though we don't intend it to be a constructor.
  • Attempts to use it as a constructor (new square) will work, but probably not do what the coder expected: The result of the new operation will be an object using square.prototype as its prototype (and the function's return value from n * n is thrown away).
  • If at global scope, creates a property on the global object (and thus, a global) because it's a function declaration.
  • If this were used within the function, it would be determined by how the function is called, as it's a "normal" function.

Option 2:

var square = function(n) {
  return n * n;
};
  • Not hoisted (because it's an expression), created during control flow.
  • Until ES2015, since it's an anonymous function expression, the function didn't have a name. In ES2015+, the name is derived from the variable's name (browser support may lag a bit, it seems to be low on the ES2015 support priority list).
  • Can be overwritten later via square = ...
  • Creates an object and assigns it to square.prototype, even though we don't intend it to be a constructor.
  • Attempts to use it as a constructor (new square) will work, but probably not do what the coder expected (see note on the function declaration).
  • If at global scope, creates a property on the global object (and thus, a global) since it's an old-style var variable.
  • If this were used within the function, it would be determined by how the function is called, as it's a "normal" function.

Option 2.5: (I've added this one)

var square = function square(n) {
  return n * n;
};

Exactly like Option 2, except that on ES5 and earlier, the function has a true name (square). (Note that the name doesn't have to be the same as the name of the variable, although it is in this example.) (Bugs in IE8 and earlier would end up creating two functions instead of just one; details in this blog post by T.J. Crowder [principal author of this answer].)

Option 3:

var square = (n) => {
   return n * n;
};

Could also be written:

var square = n => n * n;
  • Not hoisted (because it's an expression), created during control flow.
  • The function's name is derived from the variable's name (browser support may lag a bit, it seems to be low on the ES2015 support priority list).
  • Can be overwritten later via square = ...
  • Doesn't create an object and assign it to square.prototype.
  • Attempts to use it as a constructor (new square) will fail with an informative error (TypeError: square is not a constructor).
  • Doesn't have arguments (but you can use rest arguments instead if you need arguments functionality).
  • Per spec, requires fewer things to be "set up" when calling it, as it doesn't have its own this and doesn't have arguments. But modern JavaScript engines already optimize-out the creation of arguments if you don't use it, and it's unlikely setting up this is a significant cost.
  • If at global scope, creates a property on the global object (and thus, a global) since it's an old-style var variable.
  • Because it's an arrow function, if this were used within the function, it would use the same this as the code where the function is defined, since arrow functions close over this (rather than having it set by how they're called).

Option 4:

const square = (n) => {
   return n * n;
};

Could also be written:

const square = n => n * n;
  • Not hoisted, created during control flow
  • The function's name is derived from the variable's name (browser support may lag a bit, it seems to be low on the ES2015 support priority list).
  • Can't be overwritten later via square = ...
  • Doesn't create an object and assign it to square.prototype.
  • Attempts to use it as a constructor (new square) will fail with an informative error (TypeError: square is not a constructor).
  • Doesn't have arguments (see notes on Option 3).
  • Per spec, requires fewer things to be "set up" when calling it (see notes on Option 3).
  • If at global scope, doesn't create a property on the global object (but still creates a global), since it's an ES2015+ const.
  • Because it's an arrow function, if this were used within the function, it would use the same this as the code where the function is defined, since arrow functions close over this (rather than having it set by how they're called).

Option 5: (I've added this one)

let square = (n) => {
   return n * n;
};

Could also be written:

let square = n => n * n;

Exactly like Option 4, except it can be overwritten later via square = ...

Community
  • 1
  • 1
T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639
-2

The two main differences between old functions and ES6 arrow-functions is the length of code (arrow functions reduce the boilerplate in your case and it is IMO more elegant as you can event omit the return keyword) and the way these functions work with the context of the function call.

I would write the function as const square = n => n*n;. But generally, there is no such thing as 'preferred way to create a top level function in ES6/ES2015'.

Makyen
  • 27,758
  • 11
  • 68
  • 106
  • Okay, thanks, I guess those kind of questions are more for informal chat, than for stackoverflow, but again, thanks for your relpy, it does help, at least now I know that this is just opinion-based :) – Kasper Jul 20 '16 at 11:31
  • Unless you have read about the *context and scope* of JS functions, read the article I added to my answer. It might be useful ;) – Šimon Rozsíval Jul 20 '16 at 11:34
  • @ŠimonRozsíval, I believe the question is very valuable and your post is not like an answer. I leave a downvote and also flag as it is not an answer. – AmerllicA Apr 17 '20 at 02:17