JavaScript Functions Complete Guide

Master function declarations, arrow functions, higher-order functions, currying and more

JavaScript Functions Complete Guide

Functions are first-class citizens in JavaScript. This article covers various function features and advanced usage patterns.

Creating Functions

Function Declaration

// Function declaration (hoisted)
function greet(name) {
  return `Hello, ${name}!`;
}

// Call
greet('Alice');  // 'Hello, Alice!'

// Function declarations are hoisted
sayHi();  // Can call before declaration
function sayHi() {
  console.log('Hi!');
}

Function Expression

// Anonymous function expression
const greet = function(name) {
  return `Hello, ${name}!`;
};

// Named function expression (useful for recursion and debugging)
const factorial = function fact(n) {
  return n <= 1 ? 1 : n * fact(n - 1);
};

// Function expressions are not hoisted
// sayHi();  // ReferenceError
const sayHi = function() {
  console.log('Hi!');
};

Arrow Functions

// Basic syntax
const greet = (name) => `Hello, ${name}!`;

// Single parameter can omit parentheses
const double = n => n * 2;

// No parameters need parentheses
const getRandom = () => Math.random();

// Multiple statements need braces and return
const sum = (a, b) => {
  const result = a + b;
  return result;
};

// Returning objects need parentheses
const createUser = (name, age) => ({ name, age });

Arrow vs Regular Functions

// 1. this binding
const obj = {
  name: 'Alice',

  // Regular function: dynamic this
  regular() {
    console.log(this.name);
  },

  // Arrow function: lexical this (inherits from outer scope)
  arrow: () => {
    console.log(this.name);  // undefined (outer this)
  },

  // Callbacks in methods
  delayed() {
    // Regular function needs to save this
    const self = this;
    setTimeout(function() {
      console.log(self.name);
    }, 100);

    // Arrow function inherits this automatically
    setTimeout(() => {
      console.log(this.name);
    }, 100);
  }
};

// 2. No arguments object
function regular() {
  console.log(arguments);  // Available
}

const arrow = () => {
  // console.log(arguments);  // Error
};

// Use rest parameters instead
const arrow2 = (...args) => {
  console.log(args);
};

// 3. Cannot be used as constructor
const Arrow = () => {};
// new Arrow();  // TypeError

// 4. No prototype property
console.log(Arrow.prototype);  // undefined

Parameter Handling

Default Parameters

// Default values
function greet(name = 'Guest', greeting = 'Hello') {
  return `${greeting}, ${name}!`;
}

greet();            // 'Hello, Guest!'
greet('Alice');     // 'Hello, Alice!'
greet('Bob', 'Hi'); // 'Hi, Bob!'

// Defaults can be expressions
function createId(prefix = 'id', num = Date.now()) {
  return `${prefix}_${num}`;
}

// Using earlier parameters
function rectangle(width, height = width) {
  return width * height;
}

rectangle(5);  // 25 (square)

Rest Parameters

// Collect remaining parameters
function sum(...numbers) {
  return numbers.reduce((a, b) => a + b, 0);
}

sum(1, 2, 3);        // 6
sum(1, 2, 3, 4, 5);  // 15

// Combined with fixed parameters
function log(level, ...messages) {
  console.log(`[${level}]`, ...messages);
}

log('INFO', 'User', 'logged in');
// [INFO] User logged in

// Must be last parameter
// function invalid(...rest, last) {}  // SyntaxError

Parameter Destructuring

// Object destructuring
function createUser({ name, age, role = 'user' }) {
  return { name, age, role };
}

createUser({ name: 'Alice', age: 25 });

// With default values
function config({ host = 'localhost', port = 3000 } = {}) {
  return `${host}:${port}`;
}

config();                   // 'localhost:3000'
config({ port: 8080 });     // 'localhost:8080'

// Array destructuring
function getFirst([first, second]) {
  return first;
}

getFirst([1, 2, 3]);  // 1

this Binding

Implicit Binding

const obj = {
  name: 'Alice',
  greet() {
    console.log(`Hello, ${this.name}`);
  }
};

obj.greet();  // 'Hello, Alice'

// Method assigned to variable loses this
const fn = obj.greet;
fn();  // 'Hello, undefined'

Explicit Binding

function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const user = { name: 'Alice' };

// call - immediate invocation, args one by one
greet.call(user, 'Hello', '!');  // 'Hello, Alice!'

// apply - immediate invocation, args as array
greet.apply(user, ['Hi', '?']);  // 'Hi, Alice?'

// bind - returns bound function
const boundGreet = greet.bind(user, 'Hey');
boundGreet('...');  // 'Hey, Alice...'

// Partial application
const sayHi = greet.bind(user, 'Hi', '!');
sayHi();  // 'Hi, Alice!'

Hard Binding

// Prevent this loss
class Button {
  constructor(label) {
    this.label = label;
    // Bind method
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log(`Clicked: ${this.label}`);
  }
}

// Or use class field syntax
class Button2 {
  label = 'Button';

  // Arrow function auto-binds
  handleClick = () => {
    console.log(`Clicked: ${this.label}`);
  };
}

Higher-Order Functions

Functions as Arguments

// Callback function
function doTwice(fn) {
  fn();
  fn();
}

doTwice(() => console.log('Hello'));
// Hello
// Hello

// Array methods
const numbers = [1, 2, 3, 4, 5];

// map
const doubled = numbers.map(n => n * 2);

// filter
const evens = numbers.filter(n => n % 2 === 0);

// reduce
const sum = numbers.reduce((acc, n) => acc + n, 0);

// Custom higher-order function
function times(n, fn) {
  for (let i = 0; i < n; i++) {
    fn(i);
  }
}

times(3, i => console.log(`Iteration ${i}`));

Functions as Return Values

// Returning functions
function multiplier(factor) {
  return n => n * factor;
}

const double = multiplier(2);
const triple = multiplier(3);

double(5);  // 10
triple(5);  // 15

// Configuration functions
function createLogger(prefix) {
  return function(message) {
    console.log(`[${prefix}] ${message}`);
  };
}

const infoLog = createLogger('INFO');
const errorLog = createLogger('ERROR');

infoLog('Hello');   // [INFO] Hello
errorLog('Oops');   // [ERROR] Oops

Currying

Basic Concept

// Non-curried
function add(a, b, c) {
  return a + b + c;
}
add(1, 2, 3);  // 6

// Curried
function curriedAdd(a) {
  return function(b) {
    return function(c) {
      return a + b + c;
    };
  };
}

curriedAdd(1)(2)(3);  // 6

// Arrow function version
const curriedAdd2 = a => b => c => a + b + c;

// Partial application
const add1 = curriedAdd(1);
const add1and2 = add1(2);
add1and2(3);  // 6

Generic Curry Function

// Curry function
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return function(...moreArgs) {
      return curried.apply(this, args.concat(moreArgs));
    };
  };
}

// Usage
function sum(a, b, c) {
  return a + b + c;
}

const curriedSum = curry(sum);

curriedSum(1, 2, 3);    // 6
curriedSum(1)(2)(3);    // 6
curriedSum(1, 2)(3);    // 6
curriedSum(1)(2, 3);    // 6

Function Composition

Basic Composition

// Compose two functions
const compose = (f, g) => x => f(g(x));

const double = x => x * 2;
const addOne = x => x + 1;

const doubleThenAddOne = compose(addOne, double);
doubleThenAddOne(5);  // 11 (5 * 2 + 1)

// Pipe (left to right)
const pipe = (f, g) => x => g(f(x));

const addOneThenDouble = pipe(addOne, double);
addOneThenDouble(5);  // 12 ((5 + 1) * 2)

Multiple Function Composition

// Compose multiple functions
const compose = (...fns) =>
  fns.reduce((f, g) => (...args) => f(g(...args)));

const pipe = (...fns) =>
  fns.reduce((f, g) => (...args) => g(f(...args)));

// Usage
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;

const transform = pipe(addOne, double, square);
transform(2);  // 36 ((2 + 1) * 2)² = 36

Memoization

Basic Implementation

// Simple memoization
function memoize(fn) {
  const cache = new Map();

  return function(...args) {
    const key = JSON.stringify(args);

    if (cache.has(key)) {
      return cache.get(key);
    }

    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

// Usage
const slowFib = n => n <= 1 ? n : slowFib(n - 1) + slowFib(n - 2);
const fastFib = memoize(function fib(n) {
  return n <= 1 ? n : fastFib(n - 1) + fastFib(n - 2);
});

console.time('slow');
slowFib(35);  // Very slow
console.timeEnd('slow');

console.time('fast');
fastFib(35);  // Almost instant
console.timeEnd('fast');

Cache with TTL

function memoizeWithTTL(fn, ttl = 60000) {
  const cache = new Map();

  return function(...args) {
    const key = JSON.stringify(args);
    const cached = cache.get(key);

    if (cached && Date.now() - cached.time < ttl) {
      return cached.value;
    }

    const result = fn.apply(this, args);
    cache.set(key, { value: result, time: Date.now() });
    return result;
  };
}

Utility Functions

Debounce

function debounce(fn, delay) {
  let timer;

  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// Usage
const handleSearch = debounce((query) => {
  console.log('Searching:', query);
}, 300);

Throttle

function throttle(fn, interval) {
  let lastTime = 0;

  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

// Usage
const handleScroll = throttle(() => {
  console.log('Scrolling');
}, 100);

Once

function once(fn) {
  let called = false;
  let result;

  return function(...args) {
    if (!called) {
      called = true;
      result = fn.apply(this, args);
    }
    return result;
  };
}

const initialize = once(() => {
  console.log('Initialized');
  return { ready: true };
});

initialize();  // 'Initialized' { ready: true }
initialize();  // { ready: true } (no log)

Best Practices Summary

Function Best Practices:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   Function Design                                   │
│   ├── Single responsibility principle              │
│   ├── Keep functions short                         │
│   ├── Avoid side effects                           │
│   └── Use descriptive naming                       │
│                                                     │
│   Arrow Function Usage                              │
│   ├── Prefer arrows for callbacks                  │
│   ├── Use regular functions for object methods     │
│   └── Consider this binding needs                  │
│                                                     │
│   Performance                                       │
│   ├── Use memoization for expensive computations   │
│   ├── Use debounce/throttle for frequent calls     │
│   └── Avoid unnecessary function creation          │
│                                                     │
└─────────────────────────────────────────────────────┘
ConceptPurposeExample
CurryingParameter reusecurry(fn)(a)(b)
CompositionChain functionscompose(f, g)(x)
MemoizationCache resultsmemoize(fn)
DebounceDelay executiondebounce(fn, 300)

Master function features to write more elegant JavaScript code.