We are still cooking the magic in the way!
Functions: Declaration & Expression
What Are Functions?
Functions are one of the most important building blocks in JavaScript. A function is a reusable block of code designed to perform a specific task. Instead of writing the same logic over and over again throughout your program, you write it once inside a function and then call that function whenever you need it. Functions make your code more organized, more readable, easier to test, and easier to maintain. Every JavaScript program, from a simple form validator to a complex single-page application, is built from functions working together.
Think of a function like a recipe. You define the recipe once (the ingredients and steps), and then you can follow that recipe any time you want to cook that dish. You do not rewrite the recipe each time you cook. Similarly, you define a function once and call it as many times as needed.
Function Declarations
A function declaration (also called a function statement) is the most traditional way to define a function in JavaScript. It starts with the function keyword, followed by a name, a list of parameters in parentheses, and a block of code in curly braces. Function declarations are the most straightforward and recognizable way to create functions.
Example: Basic Function Declaration
// Defining a function
function greet() {
console.log('Hello! Welcome to JavaScript.');
}
// Calling (invoking) the function
greet();
// Output: Hello! Welcome to JavaScript.
// You can call it multiple times
greet();
greet();
// Output:
// Hello! Welcome to JavaScript.
// Hello! Welcome to JavaScript.
Example: Function with Parameters
function greetUser(name) {
console.log('Hello, ' + name + '! Welcome aboard.');
}
greetUser('Alice');
// Output: Hello, Alice! Welcome aboard.
greetUser('Bob');
// Output: Hello, Bob! Welcome aboard.
// Multiple parameters
function introduce(name, age, city) {
console.log(name + ' is ' + age + ' years old and lives in ' + city + '.');
}
introduce('Charlie', 28, 'New York');
// Output: Charlie is 28 years old and lives in New York.
calculateTotal, validateEmail, fetchUserData. A good function name makes your code self-documenting.Function Expressions
A function expression creates a function and assigns it to a variable. The function itself can be named or anonymous (without a name). Function expressions are not hoisted, which means you cannot call them before they are defined in your code. This is a key difference from function declarations and has important implications for how you organize your code.
Example: Function Expression (Anonymous)
// The function has no name -- it is anonymous
let sayGoodbye = function() {
console.log('Goodbye! See you next time.');
};
sayGoodbye();
// Output: Goodbye! See you next time.
// Function expression with parameters
let multiply = function(a, b) {
return a * b;
};
let result = multiply(6, 7);
console.log('6 x 7 = ' + result);
// Output: 6 x 7 = 42
Example: Comparing Declaration vs Expression
// Function Declaration -- can be called before it is defined
console.log(add(3, 4)); // Output: 7
function add(a, b) {
return a + b;
}
// Function Expression -- CANNOT be called before it is defined
// console.log(subtract(10, 5)); // ERROR: subtract is not defined
let subtract = function(a, b) {
return a - b;
};
console.log(subtract(10, 5)); // Output: 5
Anonymous Functions
An anonymous function is a function without a name. You have already seen one in the function expression example above. Anonymous functions are commonly used as arguments passed to other functions (callbacks), in event handlers, and in immediately invoked function expressions (IIFEs). They are everywhere in JavaScript.
Example: Anonymous Functions in Practice
// Anonymous function as an event handler (conceptual example)
// document.getElementById('btn').addEventListener('click', function() {
// console.log('Button was clicked!');
// });
// Anonymous function with setTimeout
setTimeout(function() {
console.log('This message appears after 2 seconds');
}, 2000);
// Anonymous function with array methods
let numbers = [1, 2, 3, 4, 5];
let doubled = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
console.log('Doubled: ' + doubled);
// Output: Doubled: 2,4,6,8,10
Named Function Expressions
A named function expression is a function expression where the function also has its own name. This name is only accessible inside the function itself (useful for recursion) and appears in stack traces for easier debugging. The external variable name is what you use to call the function from outside.
Example: Named Function Expression
// The function has two names:
// 'factorial' (internal) and 'calculateFactorial' (external)
let calculateFactorial = function factorial(n) {
if (n <= 1) {
return 1;
}
// Using the internal name for recursion
return n * factorial(n - 1);
};
console.log(calculateFactorial(5)); // Output: 120
console.log(calculateFactorial(7)); // Output: 5040
// 'factorial' is NOT accessible outside the function
// console.log(factorial(5)); // ERROR: factorial is not defined
Example: Debugging Benefit of Named Expressions
// Anonymous -- harder to debug
let processA = function(data) {
// If an error occurs here, stack trace shows "anonymous"
return data.toUpperCase();
};
// Named -- easier to debug
let processB = function processData(data) {
// If an error occurs here, stack trace shows "processData"
return data.toUpperCase();
};
console.log(processA('hello')); // Output: HELLO
console.log(processB('world')); // Output: WORLD
Immediately Invoked Function Expressions (IIFE)
An IIFE is a function that runs immediately after it is defined. You wrap the function in parentheses to make it an expression, then add another pair of parentheses to invoke it immediately. IIFEs were historically used to create private scopes and avoid polluting the global namespace. While modern JavaScript has block scoping with let and const, IIFEs are still used in libraries, configuration scripts, and initialization code.
Example: Basic IIFE Syntax
// IIFE with no parameters
(function() {
let message = 'I run immediately!';
console.log(message);
})();
// Output: I run immediately!
// The variable 'message' is NOT accessible here
// console.log(message); // ERROR: message is not defined
Example: IIFE with Parameters
// IIFE that accepts arguments
(function(name, version) {
console.log('App: ' + name + ' v' + version);
console.log('Initializing...');
})('MyApp', '1.0.0');
// Output:
// App: MyApp v1.0.0
// Initializing...
Example: IIFE for Creating a Private Scope
// Counter module using IIFE
let counter = (function() {
let count = 0; // Private variable -- not accessible outside
return {
increment: function() {
count++;
console.log('Count: ' + count);
},
decrement: function() {
count--;
console.log('Count: ' + count);
},
getCount: function() {
return count;
}
};
})();
counter.increment(); // Count: 1
counter.increment(); // Count: 2
counter.increment(); // Count: 3
counter.decrement(); // Count: 2
console.log('Current count: ' + counter.getCount()); // Current count: 2
// console.log(count); // ERROR: count is not defined
Parameters vs Arguments
These two terms are often used interchangeably, but they have distinct meanings. Parameters are the variable names listed in the function definition. They are placeholders that act as local variables within the function. Arguments are the actual values you pass to the function when you call it. Parameters receive the arguments.
Example: Parameters vs Arguments
// 'width' and 'height' are PARAMETERS (defined in the function signature)
function calculateArea(width, height) {
return width * height;
}
// 10 and 5 are ARGUMENTS (passed when calling the function)
let area = calculateArea(10, 5);
console.log('Area: ' + area); // Output: Area: 50
// Different arguments, same function
let area2 = calculateArea(7, 3);
console.log('Area: ' + area2); // Output: Area: 21
Example: What Happens with Missing or Extra Arguments
function showInfo(name, age, city) {
console.log('Name: ' + name);
console.log('Age: ' + age);
console.log('City: ' + city);
}
// All arguments provided
showInfo('Alice', 25, 'London');
// Name: Alice
// Age: 25
// City: London
// Missing argument -- becomes undefined
showInfo('Bob', 30);
// Name: Bob
// Age: 30
// City: undefined
// Extra arguments are silently ignored
showInfo('Charlie', 35, 'Paris', 'Extra', 'Values');
// Name: Charlie
// Age: 35
// City: Paris
Default Parameters
Default parameters, introduced in ES6, allow you to specify fallback values for function parameters. If an argument is not provided (or is explicitly undefined), the default value is used instead. This eliminates the need for manual checks inside the function body and makes your function signatures more expressive and self-documenting.
Example: Default Parameters
function createUser(name, role = 'member', isActive = true) {
console.log('Name: ' + name);
console.log('Role: ' + role);
console.log('Active: ' + isActive);
console.log('---');
}
// Using all defaults
createUser('Alice');
// Name: Alice
// Role: member
// Active: true
// Overriding the second parameter
createUser('Bob', 'admin');
// Name: Bob
// Role: admin
// Active: true
// Overriding all parameters
createUser('Charlie', 'moderator', false);
// Name: Charlie
// Role: moderator
// Active: false
Example: Default Parameters with Expressions
// Default values can be expressions, not just literals
function generateId(prefix = 'ID', number = Math.floor(Math.random() * 10000)) {
return prefix + '-' + number;
}
console.log(generateId()); // e.g., ID-4827
console.log(generateId('USER')); // e.g., USER-9153
console.log(generateId('ORDER', 1)); // ORDER-1
// Default values can reference earlier parameters
function createEmail(username, domain = 'gmail.com', email = username + '@' + domain) {
return email;
}
console.log(createEmail('john')); // john@gmail.com
console.log(createEmail('jane', 'company.com')); // jane@company.com
Example: Before ES6 -- Manual Default Values
// Old approach (still seen in legacy code)
function oldGreet(name, greeting) {
name = name || 'World';
greeting = greeting || 'Hello';
console.log(greeting + ', ' + name + '!');
}
oldGreet(); // Hello, World!
oldGreet('Alice'); // Hello, Alice!
oldGreet('Bob', 'Hi'); // Hi, Bob!
// Problem with the old approach:
oldGreet('', 'Hey'); // Hey, World! (empty string is falsy!)
oldGreet(0); // Hello, World! (0 is falsy!)
// ES6 approach handles these correctly
function newGreet(name = 'World', greeting = 'Hello') {
console.log(greeting + ', ' + name + '!');
}
newGreet('', 'Hey'); // Hey, ! (empty string is preserved)
newGreet(0); // Hello, 0! (0 is preserved)
undefined (or not provided). Passing null will NOT trigger the default. This is an important distinction: undefined means "not provided" while null means "intentionally empty."The arguments Object
Every non-arrow function in JavaScript has access to a special arguments object. It is an array-like object containing all the arguments passed to the function, regardless of how many parameters were defined. While modern JavaScript provides rest parameters (...args) as a better alternative, understanding the arguments object is important because you will encounter it in existing codebases.
Example: Using the arguments Object
function showArguments() {
console.log('Number of arguments: ' + arguments.length);
for (let i = 0; i < arguments.length; i++) {
console.log(' Argument ' + i + ': ' + arguments[i]);
}
}
showArguments('hello', 42, true, [1, 2, 3]);
// Output:
// Number of arguments: 4
// Argument 0: hello
// Argument 1: 42
// Argument 2: true
// Argument 3: 1,2,3
Example: Flexible Sum Function with arguments
function sum() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
console.log(sum(1, 2)); // 3
console.log(sum(10, 20, 30)); // 60
console.log(sum(1, 2, 3, 4, 5, 6)); // 21
console.log(sum()); // 0
arguments object is not a real array. It lacks array methods like map, filter, and forEach. If you need those methods, convert it first: let args = Array.from(arguments) or use the spread operator let args = [...arguments]. In modern code, prefer rest parameters (...args) which give you a real array from the start.Return Values
The return statement specifies the value a function sends back to the code that called it. A function can return any type of value: a number, a string, a boolean, an object, an array, or even another function. When a return statement is executed, the function stops immediately -- any code after the return statement is not executed. If a function does not have a return statement, it implicitly returns undefined.
Example: Functions with Return Values
// Returning a number
function square(n) {
return n * n;
}
console.log(square(5)); // 25
console.log(square(12)); // 144
// Returning a string
function formatPrice(amount, currency) {
return currency + amount.toFixed(2);
}
console.log(formatPrice(29.999, '$')); // $30.00
// Returning a boolean
function isEven(number) {
return number % 2 === 0;
}
console.log(isEven(4)); // true
console.log(isEven(7)); // false
// Returning an object
function createPerson(name, age) {
return {
name: name,
age: age,
isAdult: age >= 18
};
}
let person = createPerson('Alice', 25);
console.log(person.name); // Alice
console.log(person.isAdult); // true
Example: Early Return Pattern
// Using return to exit early (guard clause pattern)
function divide(a, b) {
if (b === 0) {
return 'Error: Cannot divide by zero';
}
return a / b;
}
console.log(divide(10, 3)); // 3.3333333333333335
console.log(divide(10, 0)); // Error: Cannot divide by zero
// Guard clauses improve readability
function processAge(age) {
if (typeof age !== 'number') {
return 'Error: Age must be a number';
}
if (age < 0) {
return 'Error: Age cannot be negative';
}
if (age > 150) {
return 'Error: Age seems unrealistic';
}
// Main logic -- only reached if all validations pass
if (age < 13) return 'child';
if (age < 18) return 'teenager';
if (age < 65) return 'adult';
return 'senior';
}
console.log(processAge(8)); // child
console.log(processAge(16)); // teenager
console.log(processAge(35)); // adult
console.log(processAge(-5)); // Error: Age cannot be negative
Example: Returning Multiple Values via Objects or Arrays
// JavaScript functions can only return one value,
// but you can return an object or array to bundle multiple values
// Returning an object
function getMinMax(arr) {
let min = arr[0];
let max = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < min) min = arr[i];
if (arr[i] > max) max = arr[i];
}
return { min: min, max: max };
}
let stats = getMinMax([34, 12, 78, 3, 56, 91, 23]);
console.log('Min: ' + stats.min + ', Max: ' + stats.max);
// Output: Min: 3, Max: 91
// Returning an array
function splitName(fullName) {
let parts = fullName.split(' ');
return [parts[0], parts[parts.length - 1]];
}
let nameParts = splitName('John Michael Smith');
console.log('First: ' + nameParts[0] + ', Last: ' + nameParts[1]);
// Output: First: John, Last: Smith
Function Hoisting
Hoisting is a JavaScript mechanism where function declarations are moved to the top of their scope during the compilation phase, before the code is executed. This means you can call a function declaration before it appears in your code. Function expressions, however, are NOT hoisted -- they behave like regular variable assignments. Understanding hoisting is crucial for avoiding subtle bugs and organizing your code effectively.
Example: Function Declaration Hoisting
// This works because function declarations are hoisted
let result1 = calculateTax(1000, 0.15);
console.log('Tax: $' + result1); // Tax: $150
function calculateTax(amount, rate) {
return amount * rate;
}
// This also works
console.log('Tax: $' + calculateTax(2000, 0.20)); // Tax: $400
Example: Function Expression NOT Hoisted
// This will cause an error!
// let result2 = computeDiscount(100, 0.1);
// TypeError: computeDiscount is not a function
let computeDiscount = function(price, discount) {
return price - (price * discount);
};
// This works -- the function is now defined
let result2 = computeDiscount(100, 0.1);
console.log('Discounted price: $' + result2); // Discounted price: $90
Example: Hoisting Behavior in Detail
// What JavaScript actually does behind the scenes:
// Your code:
sayHello();
function sayHello() {
console.log('Hello!');
}
// How JavaScript interprets it (after hoisting):
// function sayHello() {
// console.log('Hello!');
// }
// sayHello();
// With var (not let/const):
// console.log(typeof myFunc); // "undefined" -- var is hoisted but not the value
// myFunc(); // TypeError: myFunc is not a function
// var myFunc = function() { console.log('Hi'); };
// With let/const:
// myFunc2(); // ReferenceError: Cannot access 'myFunc2' before initialization
// let myFunc2 = function() { console.log('Hi'); };
Functions as Values
In JavaScript, functions are first-class citizens. This means functions can be treated just like any other value: you can assign them to variables, store them in arrays, pass them as arguments to other functions, and return them from functions. This is one of the most powerful features of JavaScript and is the foundation of functional programming patterns.
Example: Assigning Functions to Variables
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
function multiply(a, b) { return a * b; }
function divide(a, b) { return b !== 0 ? a / b : 'Error'; }
// Store functions in a variable
let operation = add;
console.log(operation(5, 3)); // 8
operation = multiply;
console.log(operation(5, 3)); // 15
// Store functions in an object
let calculator = {
add: add,
subtract: subtract,
multiply: multiply,
divide: divide
};
console.log(calculator.add(10, 5)); // 15
console.log(calculator.subtract(10, 5)); // 5
console.log(calculator.multiply(10, 5)); // 50
console.log(calculator.divide(10, 5)); // 2
Example: Storing Functions in an Array
let transformations = [
function(x) { return x * 2; },
function(x) { return x + 10; },
function(x) { return x * x; },
function(x) { return -x; }
];
let value = 5;
for (let i = 0; i < transformations.length; i++) {
let transformed = transformations[i](value);
console.log('Transformation ' + (i + 1) + ': ' + value + ' becomes ' + transformed);
}
// Output:
// Transformation 1: 5 becomes 10
// Transformation 2: 5 becomes 15
// Transformation 3: 5 becomes 25
// Transformation 4: 5 becomes -5
Example: Functions Returning Functions
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
let double = createMultiplier(2);
let triple = createMultiplier(3);
let times10 = createMultiplier(10);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(times10(5)); // 50
console.log(double(100)); // 200
// This pattern is called a "closure" --
// the inner function remembers the 'factor' variable
// from the outer function even after the outer function has returned
Introduction to Callbacks
A callback is a function that is passed as an argument to another function and is executed at a later time. Callbacks are fundamental to JavaScript, especially for handling asynchronous operations like loading data, responding to user events, and working with timers. The function that receives the callback decides when and how to call it.
Example: Basic Callback Pattern
function processData(data, callback) {
console.log('Processing: ' + data);
let result = data.toUpperCase();
callback(result);
}
function displayResult(output) {
console.log('Result: ' + output);
}
processData('hello world', displayResult);
// Output:
// Processing: hello world
// Result: HELLO WORLD
// You can also pass an anonymous function as a callback
processData('javascript', function(output) {
console.log('Callback received: ' + output);
});
// Output:
// Processing: javascript
// Callback received: JAVASCRIPT
Example: Callback for Array Processing
function transformArray(arr, transformFn) {
let result = [];
for (let i = 0; i < arr.length; i++) {
result.push(transformFn(arr[i]));
}
return result;
}
let numbers = [1, 2, 3, 4, 5];
let doubled = transformArray(numbers, function(n) {
return n * 2;
});
console.log('Doubled: ' + doubled); // Doubled: 2,4,6,8,10
let squared = transformArray(numbers, function(n) {
return n * n;
});
console.log('Squared: ' + squared); // Squared: 1,4,9,16,25
let asStrings = transformArray(numbers, function(n) {
return 'Item ' + n;
});
console.log('As strings: ' + asStrings);
// As strings: Item 1,Item 2,Item 3,Item 4,Item 5
Example: Callback for Filtering
function filterArray(arr, testFn) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (testFn(arr[i])) {
result.push(arr[i]);
}
}
return result;
}
let ages = [12, 25, 8, 32, 17, 45, 14, 28];
let adults = filterArray(ages, function(age) {
return age >= 18;
});
console.log('Adults: ' + adults); // Adults: 25,32,45,28
let teenagers = filterArray(ages, function(age) {
return age >= 13 && age <= 19;
});
console.log('Teenagers: ' + teenagers); // Teenagers: 17,14
Example: Simulating Asynchronous Operations with Callbacks
function fetchUser(userId, onSuccess, onError) {
console.log('Fetching user ' + userId + '...');
// Simulate a network delay
setTimeout(function() {
if (userId > 0) {
let user = { id: userId, name: 'User ' + userId, role: 'member' };
onSuccess(user);
} else {
onError('Invalid user ID');
}
}, 1000);
}
// Using the callback-based function
fetchUser(42,
function(user) {
console.log('Success! Found: ' + user.name);
},
function(error) {
console.log('Error: ' + error);
}
);
// After 1 second: Success! Found: User 42
fetchUser(-1,
function(user) {
console.log('Success! Found: ' + user.name);
},
function(error) {
console.log('Error: ' + error);
}
);
// After 1 second: Error: Invalid user ID
Real-World Example: Form Validator
A practical example of functions working together is building a form validator. Each validation rule is a function, and the validator composes them to check all fields. This demonstrates function declarations, return values, callbacks, and functions as values.
Example: Building a Form Validator
// Validation functions -- each returns an error message or null
function validateRequired(value, fieldName) {
if (value === '' || value === null || value === undefined) {
return fieldName + ' is required';
}
return null;
}
function validateMinLength(value, minLen, fieldName) {
if (value.length < minLen) {
return fieldName + ' must be at least ' + minLen + ' characters';
}
return null;
}
function validateEmail(value) {
if (value.indexOf('@') === -1 || value.indexOf('.') === -1) {
return 'Please enter a valid email address';
}
return null;
}
function validateAge(value) {
let age = parseInt(value);
if (isNaN(age) || age < 18 || age > 120) {
return 'Age must be a number between 18 and 120';
}
return null;
}
// Main validation function
function validateForm(formData) {
let errors = [];
let nameError = validateRequired(formData.name, 'Name');
if (nameError) errors.push(nameError);
if (!nameError) {
let lengthError = validateMinLength(formData.name, 2, 'Name');
if (lengthError) errors.push(lengthError);
}
let emailError = validateRequired(formData.email, 'Email');
if (emailError) errors.push(emailError);
if (!emailError) {
let formatError = validateEmail(formData.email);
if (formatError) errors.push(formatError);
}
let ageError = validateAge(formData.age);
if (ageError) errors.push(ageError);
return errors;
}
// Test the validator
let testData1 = { name: 'Alice', email: 'alice@example.com', age: '25' };
let errors1 = validateForm(testData1);
console.log('Test 1 errors: ' + (errors1.length === 0 ? 'None -- form is valid!' : errors1.join(', ')));
// Output: Test 1 errors: None -- form is valid!
let testData2 = { name: '', email: 'invalid', age: '15' };
let errors2 = validateForm(testData2);
console.log('Test 2 errors: ' + errors2.join('; '));
// Output: Test 2 errors: Name is required; Please enter a valid email address; Age must be a number between 18 and 120
Real-World Example: Configuration Builder
Functions that return objects are commonly used to create configuration builders. This pattern is widespread in JavaScript libraries and frameworks where you need to set up complex configurations with sensible defaults.
Example: Configuration Builder with Default Parameters
function createConfig(options) {
let defaults = {
theme: 'light',
language: 'en',
fontSize: 16,
showSidebar: true,
maxResults: 10,
debug: false
};
// Merge user options with defaults
let config = {};
for (let key in defaults) {
if (defaults.hasOwnProperty(key)) {
if (options && options.hasOwnProperty(key)) {
config[key] = options[key];
} else {
config[key] = defaults[key];
}
}
}
return config;
}
// Use with custom options
let myConfig = createConfig({ theme: 'dark', language: 'ar', debug: true });
console.log('Theme: ' + myConfig.theme); // dark
console.log('Language: ' + myConfig.language); // ar
console.log('Font Size: ' + myConfig.fontSize); // 16 (default)
console.log('Debug: ' + myConfig.debug); // true
// Use with all defaults
let defaultConfig = createConfig();
console.log('Default theme: ' + defaultConfig.theme); // light
Real-World Example: Data Pipeline with Callbacks
In real applications, you often need to process data through multiple stages. Functions and callbacks allow you to create flexible processing pipelines where each stage can be customized.
Example: Data Processing Pipeline
function pipeline(data, steps) {
let result = data;
for (let i = 0; i < steps.length; i++) {
result = steps[i](result);
console.log('Step ' + (i + 1) + ': ' + JSON.stringify(result));
}
return result;
}
// Define processing steps as functions
function removeEmpty(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== '' && arr[i] !== null && arr[i] !== undefined) {
result.push(arr[i]);
}
}
return result;
}
function trimAll(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
result.push(arr[i].toString().trim());
}
return result;
}
function lowercaseAll(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
result.push(arr[i].toLowerCase());
}
return result;
}
function removeDuplicates(arr) {
let unique = [];
for (let i = 0; i < arr.length; i++) {
let found = false;
for (let j = 0; j < unique.length; j++) {
if (unique[j] === arr[i]) {
found = true;
break;
}
}
if (!found) unique.push(arr[i]);
}
return unique;
}
// Run the pipeline
let rawData = [' Alice ', 'bob', '', ' CHARLIE ', null, 'alice', 'Bob'];
let cleanData = pipeline(rawData, [removeEmpty, trimAll, lowercaseAll, removeDuplicates]);
console.log('Final result: ' + cleanData);
// Final result: alice,bob,charlie
Best Practices for Writing Functions
- Single Responsibility -- Each function should do one thing and do it well. If a function is doing too many things, break it into smaller functions.
- Descriptive Names -- Use clear, descriptive names.
calculateTotalPriceis better thancalcordoStuff. - Keep Functions Short -- Aim for functions that are 20 lines or fewer. Shorter functions are easier to read, test, and debug.
- Limit Parameters -- Functions with more than 3 parameters become hard to use. If you need many values, pass an object instead.
- Avoid Side Effects -- Functions should ideally return a value without modifying external state. This makes them predictable and testable.
- Use Default Parameters -- Provide sensible defaults so callers only need to specify what is different from the norm.
- Return Early -- Use guard clauses to handle edge cases at the top of the function, keeping the main logic at a lower indentation level.
Practice Exercise
Complete these four challenges to strengthen your understanding of JavaScript functions:
Challenge 1: Temperature Converter. Write two functions: celsiusToFahrenheit(celsius) and fahrenheitToCelsius(fahrenheit). Then write a third function convertTemperature(value, fromUnit) that accepts a temperature and a unit string ("C" or "F") and calls the appropriate conversion function. Test with multiple values.
Challenge 2: Word Counter. Write a function analyzeText(text) that receives a string and returns an object with: the total number of characters, the number of words, the number of sentences (count periods, exclamation marks, and question marks), and the average word length. Use helper functions for each calculation.
Challenge 3: Custom Array Operations. Write three higher-order functions: myMap(array, callback) that returns a new array where each element is the result of calling the callback with the original element, myFilter(array, callback) that returns a new array with only elements for which the callback returns true, and myReduce(array, callback, initialValue) that reduces the array to a single value. Test each function with at least two different callbacks.
Challenge 4: Mini Calculator with Memory. Write an IIFE that returns a calculator object with methods: add(n), subtract(n), multiply(n), divide(n), getResult(), reset(), and getHistory(). The calculator should maintain a running result (starting at 0), and getHistory() should return an array of all operations performed (like "add 5", "multiply 3"). Test by chaining multiple operations.