Skip to content
academia.sh

Lesson 13 / 21

Function Declaration and Expression

The hoisting difference between the two definition forms, named function expressions, using functions as values, return rules, and the automatic semicolon insertion trap.

Contents

The previous lesson had checks and accumulators embedded in the loop body. Doing the same work a second time would mean copying code. The Programming Fundamentals course’s Function Definition and Invocation lesson established the reason for this separation.

JavaScript has more than one way to define a function, and the difference between them is not just syntax. This lesson compares the two basic forms; the third form, arrow functions, is covered in this topic’s fifth lesson.

Two Definition Forms

A function declaration is a statement that begins with the function keyword and carries a name. A function expression is a form that produces a value and is usually assigned to a variable.

The difference shown in the Naming and Scope Rules lesson repeats here: a declaration is hoisted together with its body, an expression is not.

console.log(averageDeclaration([21.5, 19.75]));

function averageDeclaration(array) {
  let total = 0;
  for (const value of array) total += value;
  return total / array.length;
}

const averageExpression = function (array) {
  let total = 0;
  for (const value of array) total += value;
  return total / array.length;
};
console.log(averageExpression([21.5, 19.75]));
20.625
20.625

The declaration could be called from the line before its definition. Had the expression been called before its assignment line, it would have thrown a temporal dead zone error.

The second difference between the two forms is that the expression is a value: it can be passed directly as an argument, made a property of an object, or returned from a function. A declaration cannot do this; it first has to be bound to a name.

This course’s rule: helper functions in the outermost scope are written in declaration form; functions passed to another function or carried in a variable are written in expression form. The reason is that a declaration is independent of call order and frees up the file’s reading order.

Named Function Expressions

A function expression may carry a name. This name is seen only inside the function’s own body:

const sum = function adder(array, index = 0) {
  if (index >= array.length) return 0;
  return array[index] + adder(array, index + 1);
};
console.log(sum([21.5, 19.75, 23]));
console.log(sum.name);
try {
  console.log(adder([1]));
} catch (error) {
  console.log(error.name + ": " + error.message);
}
64.25
adder
ReferenceError: adder is not defined

The inner name made the recursive call possible and showed up in the name property, but it was not accessible from outside. This lets a recursive function expression call itself without depending on the outer variable; even if the outer variable later gets bound to another value, the recursion keeps working.

Functions carry two informative properties:

function aboveThreshold(array, threshold) { return array.filter((d) => d > threshold); }
const anonymous = function (a, b) { return a + b; };
console.log(aboveThreshold.name, aboveThreshold.length);
console.log(anonymous.name, anonymous.length);
console.log(typeof aboveThreshold, aboveThreshold instanceof Object);
aboveThreshold 2
anonymous 2
function true

name is the name that appears in error messages and stack traces; it can be inferred from the variable it is assigned to. length is the expected parameter count — parameters with defaults and rest parameters are not counted, which is the next lesson’s subject.

The last line is a type observation: functions are objects. typeof gives a distinct result, but they carry properties and behave as objects.

Functions Are Values

A function being a value is what makes passing it as an argument and returning it from a function possible. This is the higher-order function concept defined in the Programming Fundamentals course’s Introduction to Functional Programming lesson.

const temperatures = [21.5, 19.75, 23, 18.25];

function thresholdFilter(threshold) {
  return function (value) {
    return value > threshold;
  };
}

const above20 = thresholdFilter(20);
console.log(temperatures.filter(above20));
console.log(temperatures.filter(thresholdFilter(22)));
[ 21.5, 23 ]
[ 23 ]

The returned function keeps carrying the threshold binding from the call it was created in. Two separate calls produced two separate bindings; the first saw 20, the second 22. This behavior is called a closure and is one of the core subjects of the next course. Here it is only being used.

A function expression can be called immediately where it is defined. This pattern is used to do work without leaking a name to the outer scope:

const report = (function () {
  const hidden = [21.5, 19.75];
  return { count: hidden.length };
})();
console.log(report);
console.log(typeof hidden);
{ count: 2 }
undefined

The name hidden is not visible outside. Module mode removes most of this need — as seen in the Ways to Run Code lesson, a module’s outermost scope is already private — but the pattern is still used to open a narrow scope.

Declarations Inside a Block

In strict mode, function declarations are block-scoped:

if (true) {
  function inBlock() {
    return "block";
  }
  console.log(inBlock());
}
try {
  console.log(inBlock());
} catch (error) {
  console.log(error.name + ": " + error.message);
}
block
ReferenceError: inBlock is not defined

This behavior is different in non-strict scripts, and compatibility rules come into play. The way to avoid ambiguity is to write conditional definitions as expressions rather than declarations: a variable declaration’s scope rule is the same in both modes.

Return Rules

A function with no return value written returns undefined. This is the value the caller sees, independent of whether the function did any work.

function logRecord(record) {
  console.log(record.station, record.temperature);
}
const result = logRecord({ station: "A1", temperature: 21.5 });
console.log("return:", result);

function earlyExit(value) {
  if (value == null) return;
  return value * 2;
}
console.log(earlyExit(null), earlyExit(10));
A1 21.5
return: undefined
undefined 20

The command–query separation from the Programming Fundamentals course applies here too: a function either does work or returns a value. logRecord does work; using its return value is meaningless.

The Automatic Semicolon Insertion Trap

JavaScript inserts a semicolon at line end in certain situations. The rule has a sharp consequence for the return statement: no line break may sit between return and the expression to be returned.

function brokenReturn() {
  return
    21.5;
}
function correctReturn() {
  return 21.5;
}
console.log(brokenReturn());
console.log(correctReturn());
undefined
21.5

In the first function, the return statement was terminated on its own line; the 21.5; line became a separate statement that never runs. No error was thrown, no warning appeared — only the wrong value came back.

The same rule applies to throw, break, continue, and the increment operators. The way to guard against it is to start the expression to be returned on the same line as return. If a long expression is being returned, its opening parenthesis is placed on the same line.

Applying It to the Measurement Script

Once the previous lessons’ checks and computations are split into functions, the script’s body shrinks to a few lines showing the flow:

function isValid(record) {
  return record.temperature != null && !Number.isNaN(record.temperature);
}

function average(records) {
  let total = 0;
  let count = 0;
  for (const record of records) {
    if (!isValid(record)) continue;
    total += record.temperature;
    count += 1;
  }
  return count === 0 ? null : total / count;
}

const records = [
  { station: "A1", temperature: 21.5 },
  { station: "A2", temperature: NaN },
  { station: "B1", temperature: 23 },
  { station: "B2", temperature: null },
];

console.log("valid:", records.filter(isValid).length);
console.log("average:", average(records));
console.log("empty list:", average([]));
valid: 2
average: 22.25
empty list: null

isValid was both passed as an argument to filter and called inside a loop — the direct benefit of a function being a value. The average function returns null for an empty list; had it divided anyway, it would have produced NaN and the error would have moved forward silently.

Summary

  • A function declaration is hoisted together with its body; a function expression comes into being on its assignment line and is in the dead zone before that.
  • A named function expression’s name is seen only inside its own body and makes recursion independent of the outer variable.
  • Functions are values: they are passed as arguments, returned, and carry properties as objects.
  • In strict mode, function declarations inside a block are block-scoped; conditional definitions are written as expressions.
  • A function with no return value written gives undefined; if a line break sits between return and the expression, the statement ends there and undefined is returned silently.

Next Step

In this lesson, functions were called with a fixed number of parameters. In real usage, an argument may be missing, extra, or its count may not be known in advance. The next lesson covers parameter rules: when default values kick in, how rest parameters collect, and why the old arguments object is not used in newer code.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close