Master JavaScript functions, parameters, return values, and understand scope management. Learn to create reusable code blocks, handle function parameters, and manage variable scope for organized programming.
Functions are the building blocks of JavaScript programming. They allow you to group code into reusable blocks, organize your program logic, and create modular, maintainable applications. Understanding functions and scope is essential for writing clean, efficient JavaScript code.
Scope determines where variables can be accessed in your code. JavaScript's scope system includes global scope, function scope, block scope, and lexical scope - each with specific rules that affect how your variables behave throughout your program.
In this lesson, you'll master function creation, parameter handling, return values, and JavaScript's scope management system. These concepts are fundamental to building any JavaScript application, from simple scripts to complex web applications.
JavaScript provides multiple ways to create functions, each with specific characteristics and use cases. Understanding these differences is crucial for writing effective code.
1. Function Declarations (Hoisted)
Function declarations are the traditional way to create functions in JavaScript:
1// Function declaration
2function greetUser(name) {
3 return "Hello, " + name + "!";
4}
5
6// Function declarations are hoisted - can be called before declaration
7console.log(sayHello("World")); // Works! Returns "Hello, World!"
8
9function sayHello(name) {
10 return "Hello, " + name + "!";
11}
12
13// Functions with multiple parameters
14function calculateArea(width, height) {
15 return width * height;
16}
17
18// Functions with no parameters
19function getCurrentTime() {
20 return new Date().toLocaleTimeString();
21}
22
23// Functions that don't return a value (return undefined)
24function displayMessage(message) {
25 console.log(message);
26 // No return statement = returns undefined
27}Key Features of Function Declarations:
2. Function Expressions (Not Hoisted)
Function expressions create functions as part of an expression:
1// Function expression assigned to variable
2const greetUser = function(name) {
3 return "Hello, " + name + "!";
4};
5
6// This would cause an error - function expressions are not hoisted
7// console.log(sayGoodbye("World")); // ReferenceError!
8
9const sayGoodbye = function(name) {
10 return "Goodbye, " + name + "!";
11};
12
13// Named function expression (useful for debugging and recursion)
14const factorial = function calculateFactorial(n) {
15 if (n <= 1) return 1;
16 return n * calculateFactorial(n - 1); // Can use the name inside
17};
18
19// Anonymous function expression
20const multiply = function(a, b) {
21 return a * b;
22};
23
24// Function expressions in objects
25const calculator = {
26 add: function(a, b) {
27 return a + b;
28 },
29 subtract: function(a, b) {
30 return a - b;
31 }
32};Key Features of Function Expressions:
3. Arrow Functions (ES6)
Arrow functions provide a more concise syntax and have different this behavior:
1// Basic arrow function syntax
2const greet = (name) => {
3 return "Hello, " + name + "!";
4};
5
6// Shorter syntax for single expressions (implicit return)
7const greetShort = name => "Hello, " + name + "!";
8
9// Multiple parameters
10const add = (a, b) => a + b;
11
12// No parameters
13const getCurrentDate = () => new Date();
14
15// Single parameter (parentheses optional)
16const square = x => x * x;
17const squareExplicit = (x) => x * x; // Parentheses for clarity
18
19// Multiple statements require curly braces and explicit return
20const processUser = (name, age) => {
21 const formattedName = name.toUpperCase();
22 const category = age >= 18 ? 'Adult' : 'Minor';
23 return `${formattedName} (${category})`;
24};
25
26// Arrow functions in array methods
27const numbers = [1, 2, 3, 4, 5];
28const doubled = numbers.map(n => n * 2);
29const evenNumbers = numbers.filter(n => n % 2 === 0);
30const sum = numbers.reduce((acc, n) => acc + n, 0);Key Features of Arrow Functions:
this**: Don't have their own this contextnew with arrow functionsarguments object**: Use rest parameters insteadWhen to Use Each Function Type:
Use Function Declarations for:
this contextUse Function Expressions for:
Use Arrow Functions for:
this1// Practical example combining all types
2class UserManager {
3 constructor() {
4 this.users = [];
5
6 // Arrow function maintains 'this' binding
7 this.addUser = (name, age) => {
8 this.users.push({ name, age });
9 };
10 }
11
12 // Method using function expression
13 getAdults: function() {
14 return this.users.filter(user => user.age >= 18);
15 }
16
17 // Traditional method declaration
18 displayUsers() {
19 this.users.forEach(user => {
20 console.log(`${user.name} (${user.age})`);
21 });
22 }
23}Best Practices:
this behavior** when choosing between regular and arrow functionsFunctions receive data through parameters and can send data back through return values. Understanding parameter handling is essential for creating flexible, reusable functions.
1. Basic Parameters and Arguments
1// Basic function with parameters
2function greetUser(firstName, lastName) {
3 console.log("Parameters received:", arguments.length);
4 return `Hello, ${firstName} ${lastName}!`;
5}
6
7// Function calls with arguments
8console.log(greetUser("Alice", "Johnson")); // "Hello, Alice Johnson!"
9console.log(greetUser("Bob")); // "Hello, Bob undefined!"
10console.log(greetUser("Alice", "Johnson", "Extra")); // Still works, ignores extra
11
12// The arguments object (available in regular functions, not arrow functions)
13function showArguments() {
14 console.log("Total arguments:", arguments.length);
15 for (let i = 0; i < arguments.length; i++) {
16 console.log(`Argument ${i}: ${arguments[i]}`);
17 }
18}
19
20showArguments("apple", "banana", "cherry");
21// Total arguments: 3
22// Argument 0: apple
23// Argument 1: banana
24// Argument 2: cherry2. Default Parameters
ES6 introduced default parameter values for more robust functions:
1// Default parameters
2function greetUser(name = "Guest", greeting = "Hello") {
3 return `${greeting}, ${name}!`;
4}
5
6console.log(greetUser()); // "Hello, Guest!"
7console.log(greetUser("Alice")); // "Hello, Alice!"
8console.log(greetUser("Bob", "Hi")); // "Hi, Bob!"
9
10// Default parameters can use other parameters
11function createUser(name, role = "user", permissions = getDefaultPermissions(role)) {
12 return { name, role, permissions };
13}
14
15function getDefaultPermissions(role) {
16 return role === "admin" ? ["read", "write", "delete"] : ["read"];
17}
18
19// Default parameters with expressions
20function logMessage(message, timestamp = new Date(), level = "info") {
21 console.log(`[${timestamp.toISOString()}] [${level.toUpperCase()}] ${message}`);
22}
23
24// Using undefined triggers default values
25function calculatePrice(amount, tax = 0.1, discount = 0) {
26 const totalTax = amount * tax;
27 const totalDiscount = amount * discount;
28 return amount + totalTax - totalDiscount;
29}
30
31console.log(calculatePrice(100)); // Uses defaults: tax=0.1, discount=0
32console.log(calculatePrice(100, undefined, 0.2)); // Uses default tax, custom discount3. Rest Parameters
Rest parameters collect multiple arguments into an array:
1// Rest parameters collect remaining arguments
2function sum(...numbers) {
3 console.log("Numbers received:", numbers); // Array of all arguments
4 return numbers.reduce((total, num) => total + num, 0);
5}
6
7console.log(sum(1, 2, 3)); // 6
8console.log(sum(1, 2, 3, 4, 5)); // 15
9console.log(sum()); // 0 (empty array)
10
11// Rest parameters with other parameters
12function createMessage(greeting, ...names) {
13 if (names.length === 0) {
14 return greeting;
15 } else if (names.length === 1) {
16 return `${greeting}, ${names[0]}!`;
17 } else {
18 const lastPerson = names.pop();
19 return `${greeting}, ${names.join(", ")} and ${lastPerson}!`;
20 }
21}
22
23console.log(createMessage("Hello")); // "Hello"
24console.log(createMessage("Hello", "Alice")); // "Hello, Alice!"
25console.log(createMessage("Hello", "Alice", "Bob")); // "Hello, Alice and Bob!"
26console.log(createMessage("Hello", "Alice", "Bob", "Charlie")); // "Hello, Alice, Bob and Charlie!"
27
28// Rest parameters in arrow functions (no arguments object)
29const multiply = (...numbers) => {
30 return numbers.reduce((product, num) => product * num, 1);
31};
32
33console.log(multiply(2, 3, 4)); // 244. Destructuring Parameters
Extract values from objects and arrays directly in parameter lists:
1// Object destructuring in parameters
2function createUserProfile({ name, age, email, city = "Unknown" }) {
3 return `${name} (${age}) from ${city} - ${email}`;
4}
5
6const user = { name: "Alice", age: 30, email: "[email protected]" };
7console.log(createUserProfile(user));
8
9// Array destructuring in parameters
10function processCoordinates([x, y, z = 0]) {
11 return { x, y, z };
12}
13
14const coords = [10, 20];
15console.log(processCoordinates(coords)); // { x: 10, y: 20, z: 0 }
16
17// Complex destructuring with defaults
18function setupUser({
19 name,
20 preferences = { theme: "light", language: "en" },
21 permissions = ["read"],
22 ...otherData
23}) {
24 return {
25 name,
26 preferences,
27 permissions,
28 metadata: otherData
29 };
30}
31
32const userData = {
33 name: "Bob",
34 age: 25,
35 department: "Engineering"
36};
37
38console.log(setupUser(userData));5. Return Values and Early Returns
1// Simple return values
2function add(a, b) {
3 return a + b;
4}
5
6function isEven(number) {
7 return number % 2 === 0;
8}
9
10// Multiple return statements (early returns)
11function classifyNumber(num) {
12 if (typeof num !== 'number') {
13 return "Invalid input";
14 }
15
16 if (num === 0) {
17 return "Zero";
18 }
19
20 if (num > 0) {
21 return "Positive";
22 }
23
24 return "Negative";
25}
26
27// Returning complex data structures
28function analyzeText(text) {
29 if (!text) {
30 return { error: "No text provided" };
31 }
32
33 const words = text.split(' ').filter(word => word.length > 0);
34 const characters = text.length;
35 const sentences = text.split('.').length - 1;
36
37 return {
38 words: words.length,
39 characters,
40 sentences,
41 averageWordLength: words.reduce((sum, word) => sum + word.length, 0) / words.length,
42 longestWord: words.reduce((longest, current) =>
43 current.length > longest.length ? current : longest, ""
44 )
45 };
46}
47
48const analysis = analyzeText("Hello world. This is a test sentence.");
49console.log(analysis);
50
51// Functions that return functions (higher-order functions)
52function createMultiplier(factor) {
53 return function(number) {
54 return number * factor;
55 };
56}
57
58const double = createMultiplier(2);
59const triple = createMultiplier(3);
60
61console.log(double(5)); // 10
62console.log(triple(5)); // 15
63
64// Arrow function returning arrow function
65const createAdder = (x) => (y) => x + y;
66const addFive = createAdder(5);
67console.log(addFive(10)); // 156. Function Parameters Best Practices
1// Validate parameters
2function divide(a, b) {
3 // Parameter validation
4 if (typeof a !== 'number' || typeof b !== 'number') {
5 throw new Error("Both parameters must be numbers");
6 }
7
8 if (b === 0) {
9 throw new Error("Cannot divide by zero");
10 }
11
12 return a / b;
13}
14
15// Use descriptive parameter names
16function calculateLoanPayment(principal, interestRate, termInMonths) {
17 const monthlyRate = interestRate / 12;
18 const payment = (principal * monthlyRate * Math.pow(1 + monthlyRate, termInMonths)) /
19 (Math.pow(1 + monthlyRate, termInMonths) - 1);
20 return Math.round(payment * 100) / 100; // Round to 2 decimal places
21}
22
23// Options object pattern for many parameters
24function createChart(data, options = {}) {
25 const {
26 width = 400,
27 height = 300,
28 type = 'bar',
29 colors = ['#3498db', '#e74c3c', '#2ecc71'],
30 title = 'Chart',
31 showLegend = true,
32 animate = false
33 } = options;
34
35 return {
36 data,
37 config: { width, height, type, colors, title, showLegend, animate }
38 };
39}
40
41// Usage with options object
42const chart = createChart([1, 2, 3, 4], {
43 width: 600,
44 type: 'line',
45 title: 'Sales Data',
46 animate: true
47});Best Practices for Parameters and Returns:
Scope determines where variables can be accessed in your JavaScript code. Understanding scope is crucial for writing predictable, maintainable programs and avoiding common bugs.
1. Global Scope
Variables declared outside any function or block have global scope:
1// Global scope variables
2var globalVar = "I'm global with var";
3let globalLet = "I'm global with let";
4const globalConst = "I'm global with const";
5
6// Global variables are accessible everywhere
7function demonstrateGlobalAccess() {
8 console.log(globalVar); // Accessible
9 console.log(globalLet); // Accessible
10 console.log(globalConst); // Accessible
11}
12
13// Global variables become properties of the global object (window in browsers)
14console.log(window.globalVar); // "I'm global with var" (in browser)
15console.log(window.globalLet); // undefined (let/const don't become window properties)
16
17// Implicit global variables (avoid!)
18function createImplicitGlobal() {
19 implicitGlobal = "Oops, I'm global!"; // No var/let/const = global
20}
21
22createImplicitGlobal();
23console.log(implicitGlobal); // "Oops, I'm global!" - accessible globally!
24
25// Best practice: Always use var, let, or const
26function properVariableDeclaration() {
27 const localVariable = "I'm properly scoped";
28 // This variable is only accessible within this function
29}2. Function Scope
Variables declared with var inside a function are function-scoped:
1function demonstrateFunctionScope() {
2 var functionScoped = "I'm function-scoped";
3 let blockScoped = "I'm also function-scoped here";
4 const alsoBlockScoped = "Me too, function-scoped here";
5
6 // These variables are accessible anywhere within the function
7 if (true) {
8 var innerVar = "var is function-scoped";
9 let innerLet = "let is block-scoped";
10 const innerConst = "const is block-scoped";
11
12 console.log(functionScoped); // Accessible
13 console.log(innerVar); // Accessible
14 console.log(innerLet); // Accessible
15 console.log(innerConst); // Accessible
16 }
17
18 console.log(innerVar); // Accessible - var is function-scoped
19 // console.log(innerLet); // ReferenceError - let is block-scoped
20 // console.log(innerConst); // ReferenceError - const is block-scoped
21}
22
23// Variables are not accessible outside their function
24// console.log(functionScoped); // ReferenceError
25
26// Function parameters are function-scoped
27function processUser(userName, userAge) {
28 // userName and userAge are accessible throughout the function
29 function displayUserInfo() {
30 console.log(`User: ${userName}, Age: ${userAge}`);
31 }
32
33 displayUserInfo(); // Can access parameters
34}
35
36// console.log(userName); // ReferenceError - parameters not accessible outside3. Block Scope
Variables declared with let and const are block-scoped:
1function demonstrateBlockScope() {
2 let outerLet = "Outer let";
3 const outerConst = "Outer const";
4 var outerVar = "Outer var";
5
6 // Block scope with if statement
7 if (true) {
8 let blockLet = "Block let";
9 const blockConst = "Block const";
10 var blockVar = "Block var"; // Still function-scoped!
11
12 console.log(outerLet); // Accessible
13 console.log(outerConst); // Accessible
14 console.log(outerVar); // Accessible
15 console.log(blockLet); // Accessible
16 console.log(blockConst); // Accessible
17 console.log(blockVar); // Accessible
18 }
19
20 console.log(outerLet); // Accessible
21 console.log(outerConst); // Accessible
22 console.log(outerVar); // Accessible
23 console.log(blockVar); // Accessible - var is function-scoped
24 // console.log(blockLet); // ReferenceError - block-scoped
25 // console.log(blockConst); // ReferenceError - block-scoped
26}
27
28// Block scope with loops
29function demonstrateLoopScope() {
30 // Classic var problem in loops
31 for (var i = 0; i < 3; i++) {
32 setTimeout(() => console.log("var i:", i), 100); // Prints 3, 3, 3
33 }
34
35 // let/const fix the loop problem
36 for (let j = 0; j < 3; j++) {
37 setTimeout(() => console.log("let j:", j), 200); // Prints 0, 1, 2
38 }
39
40 // Block scope in for loop
41 for (let k = 0; k < 3; k++) {
42 let loopVariable = k * 2;
43 console.log(loopVariable); // 0, 2, 4
44 }
45
46 // console.log(loopVariable); // ReferenceError - block-scoped
47 console.log(i); // 3 - var is function-scoped
48 // console.log(j); // ReferenceError - let is block-scoped
49}
50
51// Block scope with try-catch
52function demonstrateTryCatchScope() {
53 try {
54 let tryVariable = "In try block";
55 throw new Error("Test error");
56 } catch (error) {
57 let catchVariable = "In catch block";
58 console.log(error.message);
59 // console.log(tryVariable); // ReferenceError - different blocks
60 }
61
62 // console.log(catchVariable); // ReferenceError - block-scoped
63}4. Lexical Scope (Closure)
Functions have access to variables in their outer (enclosing) scope:
1// Lexical scope example
2function outerFunction(x) {
3 const outerVariable = "I'm in the outer function";
4
5 function innerFunction(y) {
6 const innerVariable = "I'm in the inner function";
7
8 // Inner function can access:
9 console.log(x); // Parameter from outer function
10 console.log(outerVariable); // Variable from outer function
11 console.log(y); // Own parameter
12 console.log(innerVariable); // Own variable
13
14 return x + y;
15 }
16
17 return innerFunction;
18}
19
20const closureExample = outerFunction(10);
21console.log(closureExample(5)); // 15
22
23// Practical closure example: Counter
24function createCounter(initialValue = 0) {
25 let count = initialValue;
26
27 return {
28 increment() {
29 count++;
30 return count;
31 },
32 decrement() {
33 count--;
34 return count;
35 },
36 getValue() {
37 return count;
38 },
39 reset() {
40 count = initialValue;
41 return count;
42 }
43 };
44}
45
46const counter = createCounter(5);
47console.log(counter.getValue()); // 5
48console.log(counter.increment()); // 6
49console.log(counter.increment()); // 7
50console.log(counter.decrement()); // 6
51console.log(counter.reset()); // 5
52
53// Multiple counters maintain separate scope
54const counter1 = createCounter(0);
55const counter2 = createCounter(100);
56
57console.log(counter1.increment()); // 1
58console.log(counter2.increment()); // 101
59console.log(counter1.getValue()); // 1
60console.log(counter2.getValue()); // 101
61
62// Lexical scope with loops (common interview question)
63function createFunctions() {
64 const functions = [];
65
66 // Using var (creates closure over same variable)
67 for (var i = 0; i < 3; i++) {
68 functions.push(function() {
69 return i; // All functions reference the same 'i'
70 });
71 }
72
73 return functions;
74}
75
76const badFunctions = createFunctions();
77console.log(badFunctions[0]()); // 3
78console.log(badFunctions[1]()); // 3
79console.log(badFunctions[2]()); // 3
80
81// Fixed version using let (creates new binding each iteration)
82function createFunctionsFixed() {
83 const functions = [];
84
85 for (let i = 0; i < 3; i++) {
86 functions.push(function() {
87 return i; // Each function has its own 'i'
88 });
89 }
90
91 return functions;
92}
93
94const goodFunctions = createFunctionsFixed();
95console.log(goodFunctions[0]()); // 0
96console.log(goodFunctions[1]()); // 1
97console.log(goodFunctions[2]()); // 2
98
99// Alternative fix using IIFE (Immediately Invoked Function Expression)
100function createFunctionsIIFE() {
101 const functions = [];
102
103 for (var i = 0; i < 3; i++) {
104 functions.push((function(index) {
105 return function() {
106 return index;
107 };
108 })(i));
109 }
110
111 return functions;
112}5. Scope Chain and Variable Lookup
1const globalVar = "Global";
2
3function level1() {
4 const level1Var = "Level 1";
5
6 function level2() {
7 const level2Var = "Level 2";
8
9 function level3() {
10 const level3Var = "Level 3";
11
12 // Variable lookup follows the scope chain
13 console.log(level3Var); // Found in current scope
14 console.log(level2Var); // Found in parent scope
15 console.log(level1Var); // Found in grandparent scope
16 console.log(globalVar); // Found in global scope
17
18 // Variable shadowing
19 const level1Var = "Shadowed in level 3";
20 console.log(level1Var); // "Shadowed in level 3" - shadows parent variable
21 }
22
23 level3();
24 }
25
26 level2();
27}
28
29level1();
30
31// Module pattern using scope
32const userModule = (function() {
33 // Private variables (not accessible outside)
34 let users = [];
35 let nextId = 1;
36
37 // Private functions
38 function validateUser(user) {
39 return user.name && user.email;
40 }
41
42 // Public API (returned object)
43 return {
44 addUser(name, email) {
45 const user = { id: nextId++, name, email };
46 if (validateUser(user)) {
47 users.push(user);
48 return user;
49 }
50 throw new Error("Invalid user data");
51 },
52
53 getUser(id) {
54 return users.find(user => user.id === id);
55 },
56
57 getAllUsers() {
58 return [...users]; // Return copy to prevent external modification
59 },
60
61 getUserCount() {
62 return users.length;
63 }
64 };
65})();
66
67// Usage of module
68userModule.addUser("Alice", "[email protected]");
69userModule.addUser("Bob", "[email protected]");
70console.log(userModule.getAllUsers());
71console.log(userModule.getUserCount()); // 2
72
73// Cannot access private variables
74// console.log(users); // ReferenceError
75// console.log(nextId); // ReferenceErrorBest Practices for Scope Management:
let and const** instead of var for block scopinglet for block scopingModern JavaScript includes advanced function concepts that enable powerful programming patterns. Understanding these concepts will help you write more sophisticated and maintainable code.
1. Higher-Order Functions
Functions that take other functions as parameters or return functions:
1// Functions that accept other functions as parameters
2function processArray(array, callback) {
3 const result = [];
4 for (let i = 0; i < array.length; i++) {
5 result.push(callback(array[i], i, array));
6 }
7 return result;
8}
9
10const numbers = [1, 2, 3, 4, 5];
11
12// Using with different callback functions
13const doubled = processArray(numbers, x => x * 2);
14const squared = processArray(numbers, x => x * x);
15const indexed = processArray(numbers, (x, i) => `${i}: ${x}`);
16
17console.log(doubled); // [2, 4, 6, 8, 10]
18console.log(squared); // [1, 4, 9, 16, 25]
19console.log(indexed); // ["0: 1", "1: 2", "2: 3", "3: 4", "4: 5"]
20
21// Functions that return functions (function factories)
22function createValidator(type) {
23 switch (type) {
24 case 'email':
25 return (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
26 case 'phone':
27 return (value) => /^\d{10}$/.test(value.replace(/\D/g, ''));
28 case 'required':
29 return (value) => value != null && value.toString().trim() !== '';
30 default:
31 return () => true;
32 }
33}
34
35const validateEmail = createValidator('email');
36const validatePhone = createValidator('phone');
37const validateRequired = createValidator('required');
38
39console.log(validateEmail('[email protected]')); // true
40console.log(validatePhone('123-456-7890')); // true
41console.log(validateRequired('')); // false
42
43// Function composition
44function compose(...functions) {
45 return function(value) {
46 return functions.reduceRight((acc, fn) => fn(acc), value);
47 };
48}
49
50const addTen = x => x + 10;
51const multiplyByTwo = x => x * 2;
52const subtractFive = x => x - 5;
53
54const composedFunction = compose(subtractFive, multiplyByTwo, addTen);
55console.log(composedFunction(5)); // ((5 + 10) * 2) - 5 = 252. Callback Functions and Asynchronous Patterns
1// Basic callback pattern
2function fetchUserData(userId, callback) {
3 // Simulate asynchronous operation
4 setTimeout(() => {
5 if (userId > 0) {
6 const userData = { id: userId, name: 'User ' + userId, email: `user${userId}@example.com` };
7 callback(null, userData); // Success: error is null, data is provided
8 } else {
9 callback(new Error('Invalid user ID'), null); // Error: error object, data is null
10 }
11 }, 1000);
12}
13
14// Using the callback
15fetchUserData(123, (error, data) => {
16 if (error) {
17 console.error('Error:', error.message);
18 } else {
19 console.log('User data:', data);
20 }
21});
22
23// Error-first callback pattern
24function processFile(filename, callback) {
25 // Simulate file processing
26 setTimeout(() => {
27 try {
28 if (!filename) {
29 throw new Error('Filename is required');
30 }
31
32 const result = `Processed file: ${filename}`;
33 callback(null, result);
34 } catch (error) {
35 callback(error, null);
36 }
37 }, 500);
38}
39
40// Event handler callbacks
41function createButton(text, onClick) {
42 const button = document.createElement('button');
43 button.textContent = text;
44 button.addEventListener('click', onClick);
45 return button;
46}
47
48const alertButton = createButton('Click me', () => {
49 alert('Button clicked!');
50});
51
52// Array method callbacks
53const users = [
54 { name: 'Alice', age: 25, active: true },
55 { name: 'Bob', age: 30, active: false },
56 { name: 'Charlie', age: 35, active: true }
57];
58
59// Filter with callback
60const activeUsers = users.filter(user => user.active);
61
62// Map with callback
63const userNames = users.map(user => user.name);
64
65// Reduce with callback
66const totalAge = users.reduce((sum, user) => sum + user.age, 0);
67
68// Find with callback
69const userBob = users.find(user => user.name === 'Bob');
70
71// Sort with callback
72const sortedByAge = users.sort((a, b) => a.age - b.age);
73
74console.log('Active users:', activeUsers);
75console.log('User names:', userNames);
76console.log('Total age:', totalAge);
77console.log('User Bob:', userBob);3. Immediately Invoked Function Expressions (IIFE)
1// Basic IIFE pattern
2(function() {
3 const privateVariable = "This is private";
4 console.log("IIFE executed immediately");
5})();
6
7// IIFE with parameters
8(function(name, version) {
9 console.log(`Application: ${name} v${version}`);
10})('MyApp', '1.0.0');
11
12// IIFE returning a value
13const calculator = (function() {
14 let result = 0;
15
16 return {
17 add(num) {
18 result += num;
19 return this;
20 },
21 multiply(num) {
22 result *= num;
23 return this;
24 },
25 subtract(num) {
26 result -= num;
27 return this;
28 },
29 getResult() {
30 return result;
31 },
32 reset() {
33 result = 0;
34 return this;
35 }
36 };
37})();
38
39// Method chaining with IIFE result
40const finalResult = calculator
41 .add(10)
42 .multiply(2)
43 .subtract(5)
44 .getResult();
45
46console.log(finalResult); // 15
47
48// Module pattern with IIFE
49const todoModule = (function() {
50 let todos = [];
51 let nextId = 1;
52
53 function saveTodos() {
54 localStorage.setItem('todos', JSON.stringify(todos));
55 }
56
57 function loadTodos() {
58 const stored = localStorage.getItem('todos');
59 if (stored) {
60 todos = JSON.parse(stored);
61 nextId = Math.max(...todos.map(t => t.id)) + 1;
62 }
63 }
64
65 // Initialize
66 loadTodos();
67
68 return {
69 add(text) {
70 const todo = { id: nextId++, text, completed: false };
71 todos.push(todo);
72 saveTodos();
73 return todo;
74 },
75
76 complete(id) {
77 const todo = todos.find(t => t.id === id);
78 if (todo) {
79 todo.completed = true;
80 saveTodos();
81 }
82 return todo;
83 },
84
85 remove(id) {
86 const index = todos.findIndex(t => t.id === id);
87 if (index > -1) {
88 const removed = todos.splice(index, 1)[0];
89 saveTodos();
90 return removed;
91 }
92 },
93
94 getAll() {
95 return [...todos];
96 },
97
98 getActive() {
99 return todos.filter(t => !t.completed);
100 },
101
102 getCompleted() {
103 return todos.filter(t => t.completed);
104 }
105 };
106})();4. Recursive Functions
1// Basic recursion: factorial
2function factorial(n) {
3 // Base case
4 if (n <= 1) {
5 return 1;
6 }
7 // Recursive case
8 return n * factorial(n - 1);
9}
10
11console.log(factorial(5)); // 120
12
13// Fibonacci sequence
14function fibonacci(n) {
15 if (n <= 1) return n;
16 return fibonacci(n - 1) + fibonacci(n - 2);
17}
18
19// Optimized fibonacci with memoization
20function fibonacciMemoized(n, memo = {}) {
21 if (n in memo) return memo[n];
22 if (n <= 1) return n;
23
24 memo[n] = fibonacciMemoized(n - 1, memo) + fibonacciMemoized(n - 2, memo);
25 return memo[n];
26}
27
28console.log(fibonacci(10)); // 55 (slower)
29console.log(fibonacciMemoized(10)); // 55 (faster for repeated calls)
30
31// Tree traversal
32function traverse(node, callback) {
33 callback(node);
34
35 if (node.children) {
36 node.children.forEach(child => traverse(child, callback));
37 }
38}
39
40const tree = {
41 value: 'root',
42 children: [
43 {
44 value: 'branch1',
45 children: [
46 { value: 'leaf1' },
47 { value: 'leaf2' }
48 ]
49 },
50 {
51 value: 'branch2',
52 children: [
53 { value: 'leaf3' }
54 ]
55 }
56 ]
57};
58
59traverse(tree, node => console.log(node.value));
60// Output: root, branch1, leaf1, leaf2, branch2, leaf3
61
62// Recursive object deep clone
63function deepClone(obj) {
64 if (obj === null || typeof obj !== 'object') {
65 return obj;
66 }
67
68 if (obj instanceof Date) {
69 return new Date(obj.getTime());
70 }
71
72 if (obj instanceof Array) {
73 return obj.map(item => deepClone(item));
74 }
75
76 if (typeof obj === 'object') {
77 const cloned = {};
78 for (let key in obj) {
79 if (obj.hasOwnProperty(key)) {
80 cloned[key] = deepClone(obj[key]);
81 }
82 }
83 return cloned;
84 }
85}
86
87const original = {
88 name: 'Alice',
89 age: 30,
90 hobbies: ['reading', 'coding'],
91 address: { city: 'New York', zip: '10001' }
92};
93
94const cloned = deepClone(original);
95cloned.address.city = 'Boston';
96console.log(original.address.city); // 'New York' (unchanged)
97console.log(cloned.address.city); // 'Boston'5. Function Methods: call, apply, and bind
1// Understanding 'this' context
2const person = {
3 name: 'Alice',
4 greet(greeting = 'Hello') {
5 return `${greeting}, I'm ${this.name}`;
6 }
7};
8
9console.log(person.greet()); // "Hello, I'm Alice"
10
11// Using call() to change 'this' context
12const anotherPerson = { name: 'Bob' };
13console.log(person.greet.call(anotherPerson, 'Hi')); // "Hi, I'm Bob"
14
15// Using apply() with array of arguments
16console.log(person.greet.apply(anotherPerson, ['Hey'])); // "Hey, I'm Bob"
17
18// Using bind() to create a new function with bound context
19const boundGreet = person.greet.bind(anotherPerson);
20console.log(boundGreet('Welcome')); // "Welcome, I'm Bob"
21
22// Practical example: Event handlers
23class Counter {
24 constructor(element) {
25 this.count = 0;
26 this.element = element;
27
28 // Bind methods to maintain 'this' context
29 this.increment = this.increment.bind(this);
30 this.decrement = this.decrement.bind(this);
31
32 this.setupEventListeners();
33 this.render();
34 }
35
36 increment() {
37 this.count++;
38 this.render();
39 }
40
41 decrement() {
42 this.count--;
43 this.render();
44 }
45
46 setupEventListeners() {
47 const incrementBtn = this.element.querySelector('.increment');
48 const decrementBtn = this.element.querySelector('.decrement');
49
50 // 'this' is correctly bound
51 incrementBtn.addEventListener('click', this.increment);
52 decrementBtn.addEventListener('click', this.decrement);
53 }
54
55 render() {
56 this.element.querySelector('.count').textContent = this.count;
57 }
58}
59
60// Borrowing methods from other objects
61const mathUtils = {
62 numbers: [1, 2, 3, 4, 5],
63 sum() {
64 return this.numbers.reduce((a, b) => a + b, 0);
65 },
66 average() {
67 return this.sum() / this.numbers.length;
68 }
69};
70
71const anotherSet = { numbers: [10, 20, 30] };
72
73// Borrow methods using call
74console.log(mathUtils.sum.call(anotherSet)); // 60
75console.log(mathUtils.average.call(anotherSet)); // 20
76
77// Function currying with bind
78function multiply(a, b, c) {
79 return a * b * c;
80}
81
82const multiplyByTwo = multiply.bind(null, 2);
83const multiplyByTwoAndThree = multiply.bind(null, 2, 3);
84
85console.log(multiplyByTwo(5, 10)); // 2 * 5 * 10 = 100
86console.log(multiplyByTwoAndThree(4)); // 2 * 3 * 4 = 24Best Practices for Advanced Functions:
thisbind** for event handlers in classesthis binding** to avoid context issuesMaster JavaScript functions and scope with this complete hands-on laboratory. Explore function types, parameter handling, return values, scope management, and advanced patterns through interactive examples.
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>JavaScript Functions & Scope Lab</title>
7 <style>
8 body {
9 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10 max-width: 1000px;
11 margin: 0 auto;
12 padding: 20px;
13 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
14 color: white;
15 min-height: 100vh;
16 }
17
18 .container {
19 background: rgba(255, 255, 255, 0.1);
20 backdrop-filter: blur(10px);
21 border-radius: 20px;
22 padding: 30px;
23 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
24 border: 1px solid rgba(255, 255, 255, 0.2);
25 }
26
27 h1 {
28 text-align: center;
29 margin-bottom: 30px;
30 font-size: 2.5rem;
31 text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
32 }
33
34 .section {
35 margin: 25px 0;
36 padding: 20px;
37 background: rgba(255, 255, 255, 0.1);
38 border-radius: 15px;
39 border-left: 4px solid #ffd700;
40 }
41
42 h2 {
43 color: #ffd700;
44 margin-top: 0;
45 font-size: 1.5rem;
46 }
47
48 .interactive-area {
49 background: rgba(255, 255, 255, 0.15);
50 padding: 20px;
51 border-radius: 12px;
52 margin: 15px 0;
53 border: 2px solid rgba(255, 215, 0, 0.3);
54 }
55
56 button {
57 background: linear-gradient(45deg, #ffd700, #ffed4e);
58 color: #333;
59 border: none;
60 padding: 12px 24px;
61 margin: 8px;
62 border-radius: 25px;
63 cursor: pointer;
64 font-weight: bold;
65 font-size: 14px;
66 transition: all 0.3s ease;
67 box-shadow: 0 4px 15px rgba(255, 215, 0, 0.3);
68 }
69
70 button:hover {
71 transform: translateY(-2px);
72 box-shadow: 0 6px 20px rgba(255, 215, 0, 0.4);
73 }
74
75 input[type="text"], input[type="number"] {
76 padding: 10px 15px;
77 border: 2px solid rgba(255, 255, 255, 0.3);
78 border-radius: 20px;
79 background: rgba(255, 255, 255, 0.1);
80 color: white;
81 font-size: 16px;
82 margin: 5px;
83 backdrop-filter: blur(5px);
84 width: 200px;
85 }
86
87 input::placeholder {
88 color: rgba(255, 255, 255, 0.7);
89 }
90
91 .output {
92 background: rgba(0, 0, 0, 0.2);
93 padding: 15px;
94 border-radius: 10px;
95 margin: 15px 0;
96 font-family: 'Courier New', monospace;
97 min-height: 40px;
98 border-left: 4px solid #4CAF50;
99 font-size: 14px;
100 line-height: 1.5;
101 white-space: pre-wrap;
102 }
103
104 .code-block {
105 background: rgba(0, 0, 0, 0.3);
106 padding: 15px;
107 border-radius: 8px;
108 font-family: 'Courier New', monospace;
109 font-size: 13px;
110 margin: 10px 0;
111 overflow-x: auto;
112 border: 1px solid rgba(255, 255, 255, 0.1);
113 }
114
115 .highlight {
116 background: rgba(255, 215, 0, 0.2);
117 padding: 2px 6px;
118 border-radius: 4px;
119 font-weight: bold;
120 }
121
122 .success {
123 color: #4CAF50;
124 font-weight: bold;
125 }
126
127 .info {
128 color: #2196F3;
129 font-weight: bold;
130 }
131
132 .warning {
133 color: #FF9800;
134 font-weight: bold;
135 }
136
137 .grid-demo {
138 display: grid;
139 grid-template-columns: 1fr 1fr;
140 gap: 15px;
141 margin: 15px 0;
142 }
143
144 .counter-demo {
145 background: rgba(255, 255, 255, 0.1);
146 padding: 20px;
147 border-radius: 12px;
148 margin: 15px 0;
149 text-align: center;
150 }
151
152 .counter-value {
153 font-size: 2rem;
154 font-weight: bold;
155 color: #ffd700;
156 margin: 10px 0;
157 }
158
159 @media (max-width: 768px) {
160 .grid-demo {
161 grid-template-columns: 1fr;
162 }
163 }
164 </style>
165</head>
166<body>
167 <div class="container">
168 <h1>🔧 Functions & Scope Management Lab</h1>
169
170 <div class="section">
171 <h2>1. Function Types & Declaration Methods</h2>
172 <p>Compare function declarations, expressions, and arrow functions:</p>
173
174 <div class="interactive-area">
175 <h3>Function Creation Explorer</h3>
176 <button onclick="demonstrateFunctionTypes()">🔍 Function Types</button>
177 <button onclick="demonstrateHoisting()">⬆️ Hoisting Behavior</button>
178 <button onclick="demonstrateArrowFunctions()">➡️ Arrow Functions</button>
179 <div id="functionOutput" class="output">Click a button to explore function types!</div>
180 </div>
181 </div>
182
183 <div class="section">
184 <h2>2. Parameters & Arguments Laboratory</h2>
185 <p>Master function parameters, default values, and rest parameters:</p>
186
187 <div class="interactive-area">
188 <h3>Parameter Testing Ground</h3>
189 <div class="grid-demo">
190 <div>
191 <input type="text" id="param1" placeholder="Parameter 1" value="Alice">
192 <input type="text" id="param2" placeholder="Parameter 2" value="25">
193 </div>
194 <div>
195 <input type="text" id="param3" placeholder="Parameter 3" value="Developer">
196 <input type="text" id="param4" placeholder="Parameter 4" value="New York">
197 </div>
198 </div>
199 <button onclick="testBasicParameters()">📝 Basic Parameters</button>
200 <button onclick="testDefaultParameters()">🎯 Default Parameters</button>
201 <button onclick="testRestParameters()">📋 Rest Parameters</button>
202 <div id="parameterOutput" class="output">Enter parameters and test different parameter patterns!</div>
203 </div>
204 </div>
205
206 <div class="section">
207 <h2>3. Scope Management Playground</h2>
208 <p>Visualize and understand JavaScript's scope system:</p>
209
210 <div class="interactive-area">
211 <h3>Scope Visualization</h3>
212 <button onclick="demonstrateGlobalScope()">🌍 Global Scope</button>
213 <button onclick="demonstrateFunctionScope()">🔧 Function Scope</button>
214 <button onclick="demonstrateBlockScope()">📦 Block Scope</button>
215 <button onclick="demonstrateLexicalScope()">🔗 Lexical Scope</button>
216 <div id="scopeOutput" class="output">Explore different scope types and variable accessibility!</div>
217 </div>
218 </div>
219
220 <div class="section">
221 <h2>4. Closure & Advanced Patterns</h2>
222 <p>Experience closures and higher-order function patterns:</p>
223
224 <div class="interactive-area">
225 <h3>Interactive Counter (Closure Demo)</h3>
226 <div class="counter-demo">
227 <div>Counter 1</div>
228 <div class="counter-value" id="counter1Value">0</div>
229 <button onclick="incrementCounter1()">➕ Increment</button>
230 <button onclick="decrementCounter1()">➖ Decrement</button>
231 <button onclick="resetCounter1()">🔄 Reset</button>
232 </div>
233 <div class="counter-demo">
234 <div>Counter 2</div>
235 <div class="counter-value" id="counter2Value">0</div>
236 <button onclick="incrementCounter2()">➕ Increment</button>
237 <button onclick="decrementCounter2()">➖ Decrement</button>
238 <button onclick="resetCounter2()">🔄 Reset</button>
239 </div>
240 <button onclick="demonstrateClosures()">🔗 Show Closure Code</button>
241 <div id="closureOutput" class="output">Each counter maintains its own private state through closures!</div>
242 </div>
243 </div>
244
245 <div class="section">
246 <h2>5. Higher-Order Functions Workshop</h2>
247 <p>Work with functions that accept or return other functions:</p>
248
249 <div class="interactive-area">
250 <h3>Function Composition Lab</h3>
251 <input type="number" id="compositionInput" placeholder="Enter a number" value="5">
252 <button onclick="demonstrateComposition()">🔄 Function Composition</button>
253 <button onclick="demonstrateCallbacks()">📞 Callback Functions</button>
254 <button onclick="demonstrateFactories()">🏭 Function Factories</button>
255 <div id="higherOrderOutput" class="output">Explore advanced function patterns and composition!</div>
256 </div>
257 </div>
258
259 <div class="section">
260 <h2>6. Real-World Function Applications</h2>
261 <p>Apply function concepts to practical programming scenarios:</p>
262
263 <div class="interactive-area">
264 <h3>Practical Function Examples</h3>
265 <div class="grid-demo">
266 <div>
267 <input type="text" id="userDataName" placeholder="Name" value="John Doe">
268 <input type="number" id="userDataAge" placeholder="Age" value="30">
269 </div>
270 <div>
271 <input type="text" id="userDataEmail" placeholder="Email" value="[email protected]">
272 <input type="text" id="userDataRole" placeholder="Role" value="admin">
273 </div>
274 </div>
275 <button onclick="validateUserData()">✅ Data Validation</button>
276 <button onclick="processUserData()">⚙️ Data Processing</button>
277 <button onclick="createUserProfile()">👤 Profile Creation</button>
278 <div id="practicalOutput" class="output">Enter user data to see practical function applications!</div>
279 </div>
280 </div>
281 </div>
282
283 <script>
284 // Functions & Scope Management - Interactive Demo Script
285
286 // Global variables for demonstration
287 let globalCounter = 0;
288 const globalConstant = "I'm a global constant";
289
290 // 1. FUNCTION TYPES DEMONSTRATION
291 function demonstrateFunctionTypes() {
292 const output = document.getElementById('functionOutput');
293
294 // Function declaration
295 function regularFunction(name) {
296 return `Hello from regular function, ${name}!`;
297 }
298
299 // Function expression
300 const functionExpression = function(name) {
301 return `Hello from function expression, ${name}!`;
302 };
303
304 // Arrow function
305 const arrowFunction = (name) => `Hello from arrow function, ${name}!`;
306
307 // Named function expression
308 const namedExpression = function greetUser(name) {
309 return `Hello from named expression, ${name}!`;
310 };
311
312 output.innerHTML = `317`Hello from regular function, ${name}!`;<br>
318}<br>
319<br>
320// Function expression (not hoisted)<br>
321const functionExpression = function(name) {<br>
322 return `Hello from function expression, ${name}!`;<br>
323};<br>
324<br>
325// Arrow function (ES6)<br>
326const arrowFunction = (name) => `Hello from arrow function, ${name}!`;
327 </div>
328 <div class="info">Function calls with "Alice":</div>
329 • Regular Function: <span class="highlight">${regularFunction('Alice')}</span><br>
330 • Function Expression: <span class="highlight">${functionExpression('Alice')}</span><br>
331 • Arrow Function: <span class="highlight">${arrowFunction('Alice')}</span><br>
332 • Named Expression: <span class="highlight">${namedExpression('Alice')}</span><br>
333 <br>
334 <div class="warning">💡 Key Differences:</div>
335 • Function declarations are hoisted<br>
336 • Arrow functions have no 'this' binding<br>
337 • Function expressions must be declared before use
338 `;
339 }
340
341 function demonstrateHoisting() {
342 const output = document.getElementById('functionOutput');
343
344 // This works due to hoisting
345 const hoistedResult = hoistedFunction("World");
346
347 // Function declaration (hoisted)
348 function hoistedFunction(name) {
349 return `Hello from hoisted function, ${name}!`;
350 }
351
352 // This would cause an error if uncommented
353 // const notHoistedResult = notHoistedFunction("World");
354
355 const notHoistedFunction = function(name) {
356 return `Hello from non-hoisted function, ${name}!`;
357 };
358
359 output.innerHTML = `366`Hello from hoisted function, ${name}!`;<br>
367}<br>
368<br>
369// This would cause ReferenceError if called before declaration:<br>
370// const result = notHoistedFunction("World"); // Error!<br>
371<br>
372const notHoistedFunction = function(name) {<br>
373 return `Hello from non-hoisted function, ${name}!`;<br>
374};
375 </div>
376 <div class="info">Hoisting results:</div>
377 • Hoisted function call: <span class="highlight">${hoistedResult}</span><br>
378 • Non-hoisted function: <span class="highlight">${notHoistedFunction("World")}</span><br>
379 <br>
380 <div class="warning">💡 Hoisting Rules:</div>
381 • Function declarations: Fully hoisted<br>
382 • Function expressions: Variable hoisted, function not<br>
383 • Arrow functions: Same as function expressions
384 `;
385 }
386
387 function demonstrateArrowFunctions() {
388 const output = document.getElementById('functionOutput');
389
390 // Different arrow function syntaxes
391 const simple = x => x * 2;
392 const withParens = (x) => x * 2;
393 const multiParam = (x, y) => x + y;
394 const multiLine = (x, y) => {
395 const sum = x + y;
396 const product = x * y;
397 return { sum, product };
398 };
399 const noParams = () => "No parameters!";
400 const objectReturn = x => ({ value: x, squared: x * x });
401
402 // Array methods with arrow functions
403 const numbers = [1, 2, 3, 4, 5];
404 const doubled = numbers.map(n => n * 2);
405 const evens = numbers.filter(n => n % 2 === 0);
406 const sum = numbers.reduce((acc, n) => acc + n, 0);
407
408 output.innerHTML = `435`;
436 }
437
438 // 2. PARAMETERS & ARGUMENTS
439 function testBasicParameters() {
440 const output = document.getElementById('parameterOutput');
441 const p1 = document.getElementById('param1').value;
442 const p2 = document.getElementById('param2').value;
443 const p3 = document.getElementById('param3').value;
444 const p4 = document.getElementById('param4').value;
445
446 // Function with basic parameters
447 function createUserInfo(name, age, job, city) {
448 return {
449 name: name,
450 age: parseInt(age) || 0,
451 job: job,
452 city: city,
453 fullInfo: `${name} is ${age} years old, works as a ${job} in ${city}`
454 };
455 }
456
457 // Test with different argument counts
458 const fullCall = createUserInfo(p1, p2, p3, p4);
459 const partialCall = createUserInfo(p1, p2);
460 const excessCall = createUserInfo(p1, p2, p3, p4, "Extra", "Arguments");
461
462 output.innerHTML = `471`${name} is ${age} years old...`<br>
472 };<br>
473}
474 </div>
475 <div class="info">Function call results:</div>
476 <div class="code-block">
477<strong>Full call:</strong> createUserInfo("${p1}", "${p2}", "${p3}", "${p4}")
478Result: ${JSON.stringify(fullCall, null, 2)}
479
480<strong>Partial call:</strong> createUserInfo("${p1}", "${p2}")
481Result: ${JSON.stringify(partialCall, null, 2)}
482
483<strong>Excess arguments:</strong> createUserInfo("${p1}", "${p2}", "${p3}", "${p4}", "Extra", "Arguments")
484Result: ${JSON.stringify(excessCall, null, 2)}
485 </div>
486 <div class="warning">💡 Missing parameters become undefined, excess arguments are ignored</div>
487 `;
488 }
489
490 function testDefaultParameters() {
491 const output = document.getElementById('parameterOutput');
492 const p1 = document.getElementById('param1').value;
493 const p2 = document.getElementById('param2').value;
494 const p3 = document.getElementById('param3').value;
495 const p4 = document.getElementById('param4').value;
496
497 // Function with default parameters
498 function createProfile(name = "Anonymous", age = 0, job = "Unemployed", city = "Unknown") {
499 const ageNum = parseInt(age) || 0;
500 const category = ageNum < 18 ? "Minor" : ageNum < 65 ? "Adult" : "Senior";
501
502 return {
503 name,
504 age: ageNum,
505 job,
506 city,
507 category,
508 summary: `${name} (${category}) works as ${job} in ${city}`
509 };
510 }
511
512 // Test with different parameter combinations
513 const fullDefaults = createProfile();
514 const someDefaults = createProfile(p1, p2);
515 const skipMiddle = createProfile(p1, undefined, p3, p4);
516 const allProvided = createProfile(p1, p2, p3, p4);
517
518 output.innerHTML = `543`;
544 }
545
546 function testRestParameters() {
547 const output = document.getElementById('parameterOutput');
548 const params = [
549 document.getElementById('param1').value,
550 document.getElementById('param2').value,
551 document.getElementById('param3').value,
552 document.getElementById('param4').value
553 ].filter(p => p.trim() !== '');
554
555 // Function with rest parameters
556 function processMultipleValues(category, ...values) {
557 const results = {
558 category,
559 values,
560 count: values.length,
561 analysis: {}
562 };
563
564 if (values.every(v => !isNaN(v))) {
565 const numbers = values.map(Number);
566 results.analysis = {
567 sum: numbers.reduce((a, b) => a + b, 0),
568 average: numbers.reduce((a, b) => a + b, 0) / numbers.length,
569 min: Math.min(...numbers),
570 max: Math.max(...numbers)
571 };
572 } else {
573 results.analysis = {
574 lengths: values.map(v => v.length),
575 totalLength: values.join('').length,
576 longest: values.reduce((longest, current) =>
577 current.length > longest.length ? current : longest, ''),
578 combined: values.join(' ')
579 };
580 }
581
582 return results;
583 }
584
585 // Test rest parameters
586 const result = processMultipleValues("User Input", ...params);
587 const numbersResult = processMultipleValues("Numbers", "10", "20", "30", "40");
588 const mixedResult = processMultipleValues("Mixed", "hello", "42", "world");
589
590 output.innerHTML = `615`;
616 }
617
618 // 3. SCOPE DEMONSTRATIONS
619 function demonstrateGlobalScope() {
620 const output = document.getElementById('scopeOutput');
621
622 // Access global variables
623 globalCounter++;
624
625 // Create implicit global (bad practice)
626 function createImplicitGlobal() {
627 // This creates a global variable (avoid!)
628 // implicitGlobal = "I'm accidentally global!";
629 }
630
631 output.innerHTML = `653`;
654 }
655
656 function demonstrateFunctionScope() {
657 const output = document.getElementById('scopeOutput');
658
659 function functionScopeDemo() {
660 var functionScoped = "I'm function-scoped (var)";
661 let alsoFunctionScoped = "I'm also function-scoped here (let)";
662 const constantFunctionScoped = "I'm function-scoped too (const)";
663
664 // Inner function can access outer function variables
665 function innerFunction() {
666 return {
667 canAccessVar: functionScoped,
668 canAccessLet: alsoFunctionScoped,
669 canAccessConst: constantFunctionScoped,
670 innerMessage: "Inner function can access all outer variables"
671 };
672 }
673
674 return {
675 outerMessage: "Outer function variables",
676 innerResult: innerFunction(),
677 vars: { functionScoped, alsoFunctionScoped, constantFunctionScoped }
678 };
679 }
680
681 const result = functionScopeDemo();
682
683 output.innerHTML = `704`;
705 }
706
707 function demonstrateBlockScope() {
708 const output = document.getElementById('scopeOutput');
709
710 function blockScopeDemo() {
711 let outerLet = "Outer let variable";
712 const outerConst = "Outer const variable";
713 var outerVar = "Outer var variable";
714
715 const results = {
716 beforeBlock: { outerLet, outerConst, outerVar },
717 blockTests: []
718 };
719
720 // Block scope test 1: if statement
721 if (true) {
722 let blockLet = "Block let variable";
723 const blockConst = "Block const variable";
724 var blockVar = "Block var variable"; // Still function-scoped!
725
726 results.blockTests.push({
727 block: "if statement",
728 insideBlock: { blockLet, blockConst, blockVar, outerLet, outerConst, outerVar }
729 });
730 }
731
732 // Block scope test 2: for loop
733 for (let i = 0; i < 2; i++) {
734 let loopLet = `Loop iteration ${i}`;
735 // Each iteration gets its own 'i' and 'loopLet'
736 }
737
738 results.afterBlock = {
739 outerLet,
740 outerConst,
741 outerVar,
742 blockVar: typeof blockVar !== 'undefined' ? blockVar : 'undefined (block-scoped)'
743 // blockLet and blockConst are not accessible here
744 };
745
746 return results;
747 }
748
749 const result = blockScopeDemo();
750
751 output.innerHTML = `775`;
776 }
777
778 function demonstrateLexicalScope() {
779 const output = document.getElementById('scopeOutput');
780
781 // Closure demonstration
782 function createCounter(name, initialValue = 0) {
783 let count = initialValue;
784
785 return {
786 name: name,
787 increment() {
788 count++;
789 return count;
790 },
791 decrement() {
792 count--;
793 return count;
794 },
795 getValue() {
796 return count;
797 },
798 getInfo() {
799 return `${name}: ${count}`;
800 }
801 };
802 }
803
804 // Create multiple counter instances
805 const counter1 = createCounter("Counter A", 10);
806 const counter2 = createCounter("Counter B", 100);
807
808 // Test operations
809 counter1.increment();
810 counter1.increment();
811 counter2.decrement();
812
813 output.innerHTML = `840`;
841 }
842
843 // 4. CLOSURE COUNTERS (Interactive)
844 const counter1 = createCounterClosure("Counter 1", 0);
845 const counter2 = createCounterClosure("Counter 2", 0);
846
847 function createCounterClosure(name, initialValue) {
848 let count = initialValue;
849
850 return {
851 increment() {
852 count++;
853 return count;
854 },
855 decrement() {
856 count--;
857 return count;
858 },
859 reset() {
860 count = initialValue;
861 return count;
862 },
863 getValue() {
864 return count;
865 }
866 };
867 }
868
869 function incrementCounter1() {
870 const newValue = counter1.increment();
871 document.getElementById('counter1Value').textContent = newValue;
872 }
873
874 function decrementCounter1() {
875 const newValue = counter1.decrement();
876 document.getElementById('counter1Value').textContent = newValue;
877 }
878
879 function resetCounter1() {
880 const newValue = counter1.reset();
881 document.getElementById('counter1Value').textContent = newValue;
882 }
883
884 function incrementCounter2() {
885 const newValue = counter2.increment();
886 document.getElementById('counter2Value').textContent = newValue;
887 }
888
889 function decrementCounter2() {
890 const newValue = counter2.decrement();
891 document.getElementById('counter2Value').textContent = newValue;
892 }
893
894 function resetCounter2() {
895 const newValue = counter2.reset();
896 document.getElementById('counter2Value').textContent = newValue;
897 }
898
899 function demonstrateClosures() {
900 const output = document.getElementById('closureOutput');
901
902 output.innerHTML = `935`;
936 }
937
938 // 5. HIGHER-ORDER FUNCTIONS
939 function demonstrateComposition() {
940 const output = document.getElementById('higherOrderOutput');
941 const input = parseFloat(document.getElementById('compositionInput').value) || 0;
942
943 // Individual functions
944 const addTen = x => x + 10;
945 const multiplyByTwo = x => x * 2;
946 const subtractFive = x => x - 5;
947 const square = x => x * x;
948
949 // Function composition
950 function compose(...functions) {
951 return function(value) {
952 return functions.reduceRight((acc, fn) => fn(acc), value);
953 };
954 }
955
956 // Create composed functions
957 const addThenMultiply = compose(multiplyByTwo, addTen);
958 const complexOperation = compose(subtractFive, multiplyByTwo, addTen, square);
959
960 // Apply to input
961 const step1 = addTen(input);
962 const step2 = multiplyByTwo(step1);
963 const step3 = subtractFive(step2);
964
965 const composedResult1 = addThenMultiply(input);
966 const composedResult2 = complexOperation(input);
967
968 output.innerHTML = `1002`;
1003 }
1004
1005 function demonstrateCallbacks() {
1006 const output = document.getElementById('higherOrderOutput');
1007
1008 // Higher-order function that accepts callbacks
1009 function processArray(array, ...callbacks) {
1010 let result = [...array];
1011 const steps = [`Original: [${array.join(', ')}]`];
1012
1013 callbacks.forEach((callback, index) => {
1014 result = result.map(callback);
1015 steps.push(`Step ${index + 1}: [${result.join(', ')}]`);
1016 });
1017
1018 return { result, steps };
1019 }
1020
1021 // Callback functions
1022 const double = x => x * 2;
1023 const addOne = x => x + 1;
1024 const square = x => x * x;
1025
1026 // Test array
1027 const numbers = [1, 2, 3, 4, 5];
1028
1029 // Apply callbacks
1030 const result1 = processArray(numbers, double);
1031 const result2 = processArray(numbers, double, addOne);
1032 const result3 = processArray(numbers, square, double, addOne);
1033
1034 // Event-like callback simulation
1035 function simulateEvents(callback) {
1036 const events = ['click', 'hover', 'scroll', 'resize'];
1037 return events.map(event => ({
1038 event,
1039 message: callback(event)
1040 }));
1041 }
1042
1043 const eventResults = simulateEvents(eventType => `Handled ${eventType} event`);
1044
1045 output.innerHTML = `1077`;
1078 }
1079
1080 function demonstrateFactories() {
1081 const output = document.getElementById('higherOrderOutput');
1082
1083 // Function factory for validators
1084 function createValidator(type, options = {}) {
1085 switch (type) {
1086 case 'length':
1087 return (value) => {
1088 const len = value ? value.length : 0;
1089 if (options.min && len < options.min) return `Minimum length is ${options.min}`;
1090 if (options.max && len > options.max) return `Maximum length is ${options.max}`;
1091 return null;
1092 };
1093 case 'number':
1094 return (value) => {
1095 const num = parseFloat(value);
1096 if (isNaN(num)) return 'Must be a number';
1097 if (options.min !== undefined && num < options.min) return `Minimum value is ${options.min}`;
1098 if (options.max !== undefined && num > options.max) return `Maximum value is ${options.max}`;
1099 return null;
1100 };
1101 case 'email':
1102 return (value) => {
1103 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1104 return emailRegex.test(value) ? null : 'Invalid email format';
1105 };
1106 default:
1107 return () => null;
1108 }
1109 }
1110
1111 // Create specific validators
1112 const nameValidator = createValidator('length', { min: 2, max: 50 });
1113 const ageValidator = createValidator('number', { min: 0, max: 150 });
1114 const emailValidator = createValidator('email');
1115
1116 // Test validators
1117 const testData = [
1118 { field: 'name', value: 'John', validator: nameValidator },
1119 { field: 'name', value: 'J', validator: nameValidator },
1120 { field: 'age', value: '25', validator: ageValidator },
1121 { field: 'age', value: '200', validator: ageValidator },
1122 { field: 'email', value: '[email protected]', validator: emailValidator },
1123 { field: 'email', value: 'invalid-email', validator: emailValidator }
1124 ];
1125
1126 const validationResults = testData.map(test => ({
1127 field: test.field,
1128 value: test.value,
1129 error: test.validator(test.value),
1130 valid: test.validator(test.value) === null
1131 }));
1132
1133 // Function factory for formatters
1134 function createFormatter(type, options = {}) {
1135 switch (type) {
1136 case 'currency':
1137 return (value) => {
1138 const num = parseFloat(value) || 0;
1139 return new Intl.NumberFormat('en-US', {
1140 style: 'currency',
1141 currency: options.currency || 'USD'
1142 }).format(num);
1143 };
1144 case 'date':
1145 return (value) => {
1146 const date = new Date(value);
1147 return new Intl.DateTimeFormat('en-US', {
1148 year: 'numeric',
1149 month: 'long',
1150 day: 'numeric'
1151 }).format(date);
1152 };
1153 case 'percentage':
1154 return (value) => {
1155 const num = parseFloat(value) || 0;
1156 return (num * 100).toFixed(options.decimals || 2) + '%';
1157 };
1158 default:
1159 return (value) => String(value);
1160 }
1161 }
1162
1163 const currencyFormatter = createFormatter('currency', { currency: 'USD' });
1164 const percentageFormatter = createFormatter('percentage', { decimals: 1 });
1165
1166 output.innerHTML = `1198`;
1199 }
1200
1201 // 6. PRACTICAL APPLICATIONS
1202 function validateUserData() {
1203 const output = document.getElementById('practicalOutput');
1204 const name = document.getElementById('userDataName').value;
1205 const age = document.getElementById('userDataAge').value;
1206 const email = document.getElementById('userDataEmail').value;
1207 const role = document.getElementById('userDataRole').value;
1208
1209 // Validation functions
1210 const validations = {
1211 name: (value) => {
1212 if (!value || value.trim().length < 2) return 'Name must be at least 2 characters';
1213 if (value.length > 50) return 'Name must be less than 50 characters';
1214 if (!/^[a-zA-Z\s]+$/.test(value)) return 'Name must contain only letters and spaces';
1215 return null;
1216 },
1217 age: (value) => {
1218 const num = parseInt(value);
1219 if (isNaN(num)) return 'Age must be a number';
1220 if (num < 0) return 'Age cannot be negative';
1221 if (num > 150) return 'Age must be realistic';
1222 return null;
1223 },
1224 email: (value) => {
1225 if (!value) return 'Email is required';
1226 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1227 if (!emailRegex.test(value)) return 'Invalid email format';
1228 return null;
1229 },
1230 role: (value) => {
1231 const validRoles = ['admin', 'user', 'moderator', 'guest'];
1232 if (!validRoles.includes(value.toLowerCase())) return `Role must be one of: ${validRoles.join(', ')}`;
1233 return null;
1234 }
1235 };
1236
1237 // Validate all fields
1238 const userData = { name, age, email, role };
1239 const validationResults = {};
1240 const errors = [];
1241
1242 for (const [field, value] of Object.entries(userData)) {
1243 const error = validations[field](value);
1244 validationResults[field] = {
1245 value,
1246 error,
1247 valid: error === null
1248 };
1249 if (error) errors.push(`${field}: ${error}`);
1250 }
1251
1252 const isValid = errors.length === 0;
1253
1254 output.innerHTML = `1282`;
1283 }
1284
1285 function processUserData() {
1286 const output = document.getElementById('practicalOutput');
1287 const name = document.getElementById('userDataName').value;
1288 const age = document.getElementById('userDataAge').value;
1289 const email = document.getElementById('userDataEmail').value;
1290 const role = document.getElementById('userDataRole').value;
1291
1292 // Data processing functions
1293 const processors = {
1294 formatName: (name) => {
1295 return name.trim().split(' ')
1296 .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
1297 .join(' ');
1298 },
1299
1300 calculateAgeGroup: (age) => {
1301 const ageNum = parseInt(age);
1302 if (ageNum < 18) return 'Minor';
1303 if (ageNum < 25) return 'Young Adult';
1304 if (ageNum < 40) return 'Adult';
1305 if (ageNum < 60) return 'Middle Aged';
1306 return 'Senior';
1307 },
1308
1309 extractEmailDomain: (email) => {
1310 return email.split('@')[1] || '';
1311 },
1312
1313 determinePermissions: (role) => {
1314 const permissions = {
1315 admin: ['read', 'write', 'delete', 'manage'],
1316 moderator: ['read', 'write', 'moderate'],
1317 user: ['read', 'write'],
1318 guest: ['read']
1319 };
1320 return permissions[role.toLowerCase()] || ['read'];
1321 },
1322
1323 generateUserId: (name, email) => {
1324 const namePart = name.toLowerCase().replace(/\s+/g, '');
1325 const emailPart = email.split('@')[0];
1326 const randomNum = Math.floor(Math.random() * 1000);
1327 return `${namePart}_${emailPart}_${randomNum}`;
1328 }
1329 };
1330
1331 // Process the data
1332 const processedData = {
1333 original: { name, age, email, role },
1334 processed: {
1335 formattedName: processors.formatName(name),
1336 ageGroup: processors.calculateAgeGroup(age),
1337 emailDomain: processors.extractEmailDomain(email),
1338 permissions: processors.determinePermissions(role),
1339 userId: processors.generateUserId(name, email),
1340 accountType: role.toLowerCase(),
1341 isAdult: parseInt(age) >= 18,
1342 createdAt: new Date().toISOString()
1343 }
1344 };
1345
1346 output.innerHTML = `1373`;
1374 }
1375
1376 function createUserProfile() {
1377 const output = document.getElementById('practicalOutput');
1378 const name = document.getElementById('userDataName').value;
1379 const age = document.getElementById('userDataAge').value;
1380 const email = document.getElementById('userDataEmail').value;
1381 const role = document.getElementById('userDataRole').value;
1382
1383 // Profile creation using higher-order functions
1384 function createProfileFactory(defaultSettings = {}) {
1385 return function(userData) {
1386 // Validation pipeline
1387 const validate = (data) => {
1388 const errors = [];
1389 if (!data.name || data.name.length < 2) errors.push('Invalid name');
1390 if (!data.email || !data.email.includes('@')) errors.push('Invalid email');
1391 if (!data.age || isNaN(parseInt(data.age))) errors.push('Invalid age');
1392 return { isValid: errors.length === 0, errors };
1393 };
1394
1395 // Processing pipeline
1396 const process = (data) => {
1397 const ageNum = parseInt(data.age);
1398 return {
1399 ...data,
1400 id: `user_${Date.now()}`,
1401 displayName: data.name.trim(),
1402 ageGroup: ageNum < 18 ? 'Minor' : ageNum < 65 ? 'Adult' : 'Senior',
1403 emailDomain: data.email.split('@')[1],
1404 permissions: getRolePermissions(data.role),
1405 settings: { ...defaultSettings },
1406 createdAt: new Date(),
1407 isActive: true
1408 };
1409 };
1410
1411 // Execute pipeline
1412 const validation = validate(userData);
1413 if (!validation.isValid) {
1414 return { success: false, errors: validation.errors };
1415 }
1416
1417 const processedProfile = process(userData);
1418 return { success: true, profile: processedProfile };
1419 };
1420 }
1421
1422 function getRolePermissions(role) {
1423 const permissionMap = {
1424 admin: ['create', 'read', 'update', 'delete', 'manage'],
1425 moderator: ['create', 'read', 'update', 'moderate'],
1426 user: ['create', 'read', 'update'],
1427 guest: ['read']
1428 };
1429 return permissionMap[role.toLowerCase()] || ['read'];
1430 }
1431
1432 // Create profile factory with default settings
1433 const createProfile = createProfileFactory({
1434 theme: 'light',
1435 notifications: true,
1436 language: 'en'
1437 });
1438
1439 // Create user profile
1440 const userInput = { name, age, email, role };
1441 const result = createProfile(userInput);
1442
1443 output.innerHTML = `1469`;
1470 }
1471
1472 // Initialize the demo when page loads
1473 document.addEventListener('DOMContentLoaded', function() {
1474 console.log('🔧 Functions & Scope Management Lab Loaded!');
1475 console.log('=============================================');
1476
1477 // Set initial welcome message
1478 const welcomeOutput = document.getElementById('functionOutput');
1479 welcomeOutput.innerHTML = `1489`;
1490
1491 // Set page title
1492 document.title = "🔧 JavaScript Functions & Scope Lab";
1493
1494 console.log('💡 Pro tip: Try modifying the input values to see different results!');
1495 });
1496 </script>
1497</body>
1498</html>This comprehensive demonstration covers all fundamental concepts from Lesson 3:
🎯 Complete Functions & Scope Management Coverage:
1. Function Types & Declaration Mastery:
2. Parameter & Argument Handling:
3. Comprehensive Scope Visualization:
4. Interactive Closure Demonstration:
5. Higher-Order Function Patterns:
6. Real-World Function Applications:
7. Advanced Function Concepts:
8. Professional Development Patterns:
9. Interactive Learning Features:
This interactive laboratory provides comprehensive hands-on experience with JavaScript functions and scope management, enabling students to understand these fundamental concepts through practical experimentation and real-world application examples.
Excellent work mastering JavaScript functions and scope! You now understand how to create reusable code blocks, manage variable scope, and use closures effectively. In our next lesson, we'll explore Control Flow & Conditionals, where you'll learn to make decisions in your programs using if/else statements, switch cases, comparison operators, and conditional logic patterns.
Your progress is saved in this browser, on this device only.