Block-Level Functions

In ECMAScript 3 and earlier, a function declaration occurring inside of a block (a block-level function) was technically a syntax error, but all browsers still supported it. Unfortunately, each browser that allowed the syntax behaved in a slightly different way, so it is considered a best practice to avoid function declarations inside of blocks (the best alternative is to use a function expression).

In an attempt to rein in this incompatible behavior, ECMAScript 5 strict mode introduced an error whenever a function declaration was used inside of a block in this way:

  1. "use strict";
  2. if (true) {
  3. // Throws a syntax error in ES5, not so in ES6
  4. function doSomething() {
  5. // ...
  6. }
  7. }

In ECMAScript 5, this code throws a syntax error. In ECMAScript 6, the doSomething() function is considered a block-level declaration and can be accessed and called within the same block in which it was defined. For example:

  1. "use strict";
  2. if (true) {
  3. console.log(typeof doSomething); // "function"
  4. function doSomething() {
  5. // ...
  6. }
  7. doSomething();
  8. }
  9. console.log(typeof doSomething); // "undefined"

Block level functions are hoisted to the top of the block in which they are defined, so typeof doSomething returns "function" even though it appears before the function declaration in the code. Once the if block is finished executing, doSomething() no longer exists.

Deciding When to Use Block-Level Functions

Block level functions are similar to let function expressions in that the function definition is removed once execution flows out of the block in which it’s defined. The key difference is that block level functions are hoisted to the top of the containing block. Function expressions that use let are not hoisted, as this example illustrates:

  1. "use strict";
  2. if (true) {
  3. console.log(typeof doSomething); // throws error
  4. let doSomething = function () {
  5. // ...
  6. }
  7. doSomething();
  8. }
  9. console.log(typeof doSomething);

Here, code execution stops when typeof doSomething is executed, because the let statement hasn’t been executed yet, leaving doSomething() in the TDZ. Knowing this difference, you can choose whether to use block level functions or let expressions based on whether or not you want the hoisting behavior.

Block-Level Functions in Nonstrict Mode

ECMAScript 6 also allows block-level functions in nonstrict mode, but the behavior is slightly different. Instead of hoisting these declarations to the top of the block, they are hoisted all the way to the containing function or global environment. For example:

  1. // ECMAScript 6 behavior
  2. if (true) {
  3. console.log(typeof doSomething); // "function"
  4. function doSomething() {
  5. // ...
  6. }
  7. doSomething();
  8. }
  9. console.log(typeof doSomething); // "function"

In this example, doSomething() is hoisted into the global scope so that it still exists outside of the if block. ECMAScript 6 standardized this behavior to remove the incompatible browser behaviors that previously existed, so all ECMAScript 6 runtimes should behave in the same way.

Allowing block-level functions improves your ability to declare functions in JavaScript, but ECMAScript 6 also introduced a completely new way to declare functions.