JavaScript Functions — The Basics

Introduction to JavaScript functions — function declarations, function expressions, arrow functions, parameters, default values, rest/spread, return values, and the difference between calling and referencing.

Functions are the unit of work in JavaScript

In JavaScript, functions are first-class — they are values you can store in variables, pass to other functions, return from functions, and attach as properties of objects. Everything from a single utility to an entire framework is built out of functions composing together.

This is the first of three posts on functions. It covers the foundations — how to define them and how to call them. The next two go deeper into closures, this, and higher-order patterns and modern function features such as arrow functions, destructured parameters, and async/await.

Three ways to create a function

1. Function declaration

The classic form. Hoisted in full, so it can be called above its definition.

1greet('Ali')
2
3function greet(name) {
4  return `Hello, ${name}`
5}

2. Function expression

Assigns an anonymous (or named) function to a variable. Behaves like any other value — not hoisted.

1const greet = function (name) {
2  return `Hello, ${name}`
3}

A named function expression is useful for stack traces and self-reference:

1const factorial = function fact(n) {
2  return n <= 1 ? 1 : n * fact(n - 1)
3}

3. Arrow function (ES2015)

The modern default for anonymous functions. Shorter syntax, no own this, no arguments object.

1const greet = name => `Hello, ${name}`
2
3// Multi-statement body needs braces and an explicit return
4const shippingFee = weight => {
5  if (weight < 1) return 5
6  if (weight < 10) return 10
7  return 25
8}

Declaration vs expression at a glance

FeatureDeclarationExpression / arrow
Hoisted (callable above definition)YesNo (binding is hoisted in TDZ for let/const)
Has its own thisYesExpression: yes. Arrow: no
Has an arguments objectYesExpression: yes. Arrow: no
Usable as a constructor with newYesExpression: yes. Arrow: no
name property defaults toThe declared nameThe variable it is assigned to

Calling a function

A function value and a function call are not the same thing.

1function speak() {
2  return 'hi'
3}
4
5speak // [Function: speak]   ← the function itself
6speak() // "hi"                ← the result of calling it

This trips up developers when passing callbacks:

1button.addEventListener('click', handleClick) // pass the function
2button.addEventListener('click', handleClick()) // BUG: calls now, passes the result

Parameters and arguments

A parameter is the name listed in the function signature; an argument is the value supplied at the call site.

Default parameter values (ES2015)

1function fetchPage(url, { method = 'GET', retries = 3 } = {}) {
2  // ...
3}
4
5fetchPage('/users') // method=GET, retries=3
6fetchPage('/users', { retries: 1 }) // method=GET, retries=1

A default expression is evaluated every time the function is called, so you can use it for non-constant defaults:

1function log(msg, time = new Date()) {
2  console.log(`[${time.toISOString()}] ${msg}`)
3}

Rest parameters

Collect any number of trailing arguments into a real array:

1function sum(...numbers) {
2  return numbers.reduce((a, b) => a + b, 0)
3}
4
5sum(1, 2, 3, 4) // 10

Rest must be the last parameter. There can be only one.

Spread at the call site

The same ... syntax, used as an argument, expands an iterable into individual arguments:

1const points = [3, 7, 2]
2Math.max(...points) // 7
3
4const all = [1, 2, ...points, 9] // [1, 2, 3, 7, 2, 9]

Return values

Every function returns something. If you omit return, the function returns undefined:

1function noop() {}
2noop() // undefined

Mental model: functions as values

Because functions are values, every pattern that works for values works for functions:

 1// Store in an array
 2const ops = [x => x + 1, x => x * 2]
 3
 4// Pass as an argument
 5;[1, 2, 3].map(x => x * x)
 6
 7// Return from another function
 8function adder(by) {
 9  return x => x + by
10}
11const add5 = adder(5)
12add5(10) // 15

That last example — a function returning a function that “remembers” the outer variable — is a closure. Closures, plus higher-order functions, plus the this binding, are the subject of the next post.

Summary

  • Three forms: function declaration (hoisted), function expression (not hoisted), arrow function (no own this).
  • A bare function name is the function value; only () calls it.
  • Use default parameters, rest parameters, and spread for clean, modern signatures.
  • An omitted return produces undefined. Beware automatic semicolon insertion after return.
  • Functions are values — they fit into every collection, callback, and pipeline.

Next: Functions in depth — closures, this, and higher-order patterns.