Functions

  • 7.1 Use named function expressions instead of function declarations. eslint: func-style

    Why? Function declarations are hoisted, which means that it’s easy - too easy - to reference the function before it is defined in the file. This harms readability and maintainability. If you find that a function’s definition is large or complex enough that it is interfering with understanding the rest of the file, then perhaps it’s time to extract it to its own module! Don’t forget to explicitly name the expression, regardless of whether or not the name is inferred from the containing variable (which is often the case in modern browsers or when using compilers such as Babel). This eliminates any assumptions made about the Error’s call stack. (Discussion)

    1. // bad
    2. function foo() {
    3. // ...
    4. }
    5. // bad
    6. const foo = function () {
    7. // ...
    8. };
    9. // good
    10. // lexical name distinguished from the variable-referenced invocation(s)
    11. const short = function longUniqueMoreDescriptiveLexicalFoo() {
    12. // ...
    13. };

  • 7.2 Wrap immediately invoked function expressions in parentheses. eslint: wrap-iife

    Why? An immediately invoked function expression is a single unit - wrapping both it, and its invocation parens, in parens, cleanly expresses this. Note that in a world with modules everywhere, you almost never need an IIFE.

    1. // immediately-invoked function expression (IIFE)
    2. (function () {
    3. console.log('Welcome to the Internet. Please follow me.');
    4. }());

  • 7.3 Never declare a function in a non-function block (if, while, etc). Assign the function to a variable instead. Browsers will allow you to do it, but they all interpret it differently, which is bad news bears. eslint: no-loop-func

  • 7.4 Note: ECMA-262 defines a block as a list of statements. A function declaration is not a statement.

    1. // bad
    2. if (currentUser) {
    3. function test() {
    4. console.log('Nope.');
    5. }
    6. }
    7. // good
    8. let test;
    9. if (currentUser) {
    10. test = () => {
    11. console.log('Yup.');
    12. };
    13. }

  • 7.5 Never name a parameter arguments. This will take precedence over the arguments object that is given to every function scope.

    1. // bad
    2. function foo(name, options, arguments) {
    3. // ...
    4. }
    5. // good
    6. function foo(name, options, args) {
    7. // ...
    8. }

  • 7.6 Never use arguments, opt to use rest syntax ... instead. eslint: prefer-rest-params

    Why? ... is explicit about which arguments you want pulled. Plus, rest arguments are a real Array, and not merely Array-like like arguments.

    1. // bad
    2. function concatenateAll() {
    3. const args = Array.prototype.slice.call(arguments);
    4. return args.join('');
    5. }
    6. // good
    7. function concatenateAll(...args) {
    8. return args.join('');
    9. }

  • 7.7 Use default parameter syntax rather than mutating function arguments.

    1. // really bad
    2. function handleThings(opts) {
    3. // No! We shouldn’t mutate function arguments.
    4. // Double bad: if opts is falsy it'll be set to an object which may
    5. // be what you want but it can introduce subtle bugs.
    6. opts = opts || {};
    7. // ...
    8. }
    9. // still bad
    10. function handleThings(opts) {
    11. if (opts === void 0) {
    12. opts = {};
    13. }
    14. // ...
    15. }
    16. // good
    17. function handleThings(opts = {}) {
    18. // ...
    19. }

  • 7.8 Avoid side effects with default parameters.

    Why? They are confusing to reason about.

    1. var b = 1;
    2. // bad
    3. function count(a = b++) {
    4. console.log(a);
    5. }
    6. count(); // 1
    7. count(); // 2
    8. count(3); // 3
    9. count(); // 3

  • 7.9 Always put default parameters last.

    1. // bad
    2. function handleThings(opts = {}, name) {
    3. // ...
    4. }
    5. // good
    6. function handleThings(name, opts = {}) {
    7. // ...
    8. }

  • 7.10 Never use the Function constructor to create a new function. eslint: no-new-func

    Why? Creating a function in this way evaluates a string similarly to eval(), which opens vulnerabilities.

    1. // bad
    2. var add = new Function('a', 'b', 'return a + b');
    3. // still bad
    4. var subtract = Function('a', 'b', 'return a - b');

  • 7.11 Spacing in a function signature. eslint: space-before-function-paren space-before-blocks

    Why? Consistency is good, and you shouldn’t have to add or remove a space when adding or removing a name.

    1. // bad
    2. const f = function(){};
    3. const g = function (){};
    4. const h = function() {};
    5. // good
    6. const x = function () {};
    7. const y = function a() {};

  • 7.12 Never mutate parameters. eslint: no-param-reassign

    Why? Manipulating objects passed in as parameters can cause unwanted variable side effects in the original caller.

    1. // bad
    2. function f1(obj) {
    3. obj.key = 1;
    4. }
    5. // good
    6. function f2(obj) {
    7. const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
    8. }

  • 7.13 Never reassign parameters. eslint: no-param-reassign

    Why? Reassigning parameters can lead to unexpected behavior, especially when accessing the arguments object. It can also cause optimization issues, especially in V8.

    1. // bad
    2. function f1(a) {
    3. a = 1;
    4. // ...
    5. }
    6. function f2(a) {
    7. if (!a) { a = 1; }
    8. // ...
    9. }
    10. // good
    11. function f3(a) {
    12. const b = a || 1;
    13. // ...
    14. }
    15. function f4(a = 1) {
    16. // ...
    17. }

  • 7.14 Prefer the use of the spread operator ... to call variadic functions. eslint: prefer-spread

    Why? It’s cleaner, you don’t need to supply a context, and you can not easily compose new with apply.

    1. // bad
    2. const x = [1, 2, 3, 4, 5];
    3. console.log.apply(console, x);
    4. // good
    5. const x = [1, 2, 3, 4, 5];
    6. console.log(...x);
    7. // bad
    8. new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));
    9. // good
    10. new Date(...[2016, 8, 5]);

  • 7.15 Functions with multiline signatures, or invocations, should be indented just like every other multiline list in this guide: with each item on a line by itself, with a trailing comma on the last item. eslint: function-paren-newline

    1. // bad
    2. function foo(bar,
    3. baz,
    4. quux) {
    5. // ...
    6. }
    7. // good
    8. function foo(
    9. bar,
    10. baz,
    11. quux,
    12. ) {
    13. // ...
    14. }
    15. // bad
    16. console.log(foo,
    17. bar,
    18. baz);
    19. // good
    20. console.log(
    21. foo,
    22. bar,
    23. baz,
    24. );