CACS254 Scripting Language

Scripting LanguageUnit 314 min read

JavaScript Functions & Methods: Definitions, Scope, Closures, Arrow Functions & Built-in Methods

Unit 3 of Scripting Language covers JavaScript functions (declaration, expression, parameters, rest/spread, recursion), scope (global, local, block, function), closures, arrow functions, and built-in methods (map, filter, reduce, forEach). Includes real-world examples from eSewa, Daraz, and Ncell, plus visual traces of

TAKEAWAYS

  • JavaScript functions are first-class citizens: they can be passed as arguments, returned from other functions, and assigned to variables.
  • Scope rules (global, local, block, function) determine variable accessibility, with let/const enforcing block scope.
  • Closures capture the lexical environment of their creation, enabling data privacy and function factories.
  • Arrow functions (=>) are concise but lack this binding and arguments object, making them unsuitable for object methods or callbacks needing dynamic context.
  • Built-in array methods like map, filter, and reduce transform, filter, and aggregate data without mutating the original array.
  • Recursion is a powerful technique for problems with self-similar subproblems (e.g., tree traversals, factorial calculation).

1. JavaScript Functions: Basics and Types

JavaScript functions are reusable blocks of code that perform a specific task. They can be defined in multiple ways and have unique behaviors like first-class functions and closures.

1.1 Function Declaration vs. Function Expression

flowchart TD
    A["Function Declaration"] -->|"Syntax"| B["function name(params) { ... }"]
    C["Function Expression"] -->|"Syntax"| D["const name = function(params) { ... }"]
    E["Arrow Function"] -->|"Syntax"| F["const name = (params) => { ... }"]
    B -->|"Hoisted"| G["Can be called before declaration"]
    D -->|"Not Hoisted"| H["Must be defined before use"]
    F -->|"Lexical `this`"| I["No `this` binding; no `arguments`"]

Example: Function Declaration vs. Expression

// Declaration (hoisted)
function add(a, b) {
    return a + b;
}

// Expression (not hoisted)
const subtract = function(a, b) {
    return a - b;
};

// Arrow function (lexical `this`)
const multiply = (a, b) => a * b;

1.2 Parameters and Arguments

  • Parameters: Variables listed in the function definition.
  • Arguments: Actual values passed when calling the function.
  • Rest Parameters (...args): Collects all arguments into an array.
  • Default Parameters: Assign default values if arguments are missing.
// Rest parameters
function sum(...numbers) {
    return numbers.reduce((total, num) => total + num, 0);
}

// Default parameters
function greet(name = "Guest") {
    return `Hello, ${name}!`;
}

Trace: sum(1, 2, 3) Execution

Step numbers (array) reduce accumulator Result
1 [1, 2, 3] 1 (initial) 1
2 [2, 3] 1 + 2 = 3 3
3 [3] 3 + 3 = 6 6
4 [] 6 + 3 = 9 9

2. Scope in JavaScript

Scope determines where variables are accessible. JavaScript has global, local (function), and block scope.

2.1 Scope Rules Visualized

Example: Scope in Action

var globalVar = "I'm global";
let blockVar = "I'm block-scoped";

function scopeTest() {
    var functionVar = "I'm function-scoped";
    if (true) {
        let blockVar = "I'm block-scoped inside!";
        console.log(blockVar); // "I'm block-scoped inside!"
    }
    console.log(globalVar); // "I'm global"
    console.log(functionVar); // "I'm function-scoped"
    // console.log(blockVar); // ReferenceError (block scope)
}

Key Differences

Scope Type Declaration Keyword Lifetime Accessibility
Global var/let/const Entire script Everywhere
Function var/let/const Inside function Only within function
Block let/const Inside {} Only within block

3. Closures: The Power of Lexical Scoping

A closure is a function that retains access to its lexical scope even after the outer function has executed.

3.1 How Closures Work

sequenceDiagram
    participant User
    participant outerFunc
    participant innerFunc
    User->>outerFunc: Call outerFunc(x)
    outerFunc->>innerFunc: Return innerFunc (captures x)
    User->>innerFunc: Call innerFunc()
    innerFunc->>User: Returns x (lexical scope preserved)

Example: Counter with Closure

function createCounter() {
    let count = 0;
    return function() {
        count++;
        return count;
    };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

Trace: Closure Execution

Step count (outer scope) innerFunc return Output
1 0 (initial) count++ → 1 1
2 1 (retained) count++ → 2 2

3.2 Real-World Use: Data Privacy in eSewa

eSewa uses closures to hide sensitive user data (e.g., API keys) from global scope:

function secureAPICall(apiKey) {
    return function(endpoint, data) {
        // apiKey is not exposed globally
        fetch(`https://api.esewa.com/${endpoint}`, {
            method: "POST",
            headers: { "Authorization": `Bearer ${apiKey}` },
            body: JSON.stringify(data)
        });
    };
}

const callEsewa = secureAPICall("hidden_api_key_123");
callEsewa("pay", { amount: 500 });

4. Arrow Functions: Concise Syntax with Caveats

Arrow functions (=>) provide a shorter syntax but do not bind their own this or have an arguments object.

4.1 Arrow Function Syntax

flowchart LR
    A["Single Param"] -->|"Syntax"| B["(param) => { ... }"]
    C["Multiple Params"] -->|"Syntax"| D["(a, b) => a + b"]
    E["No Params"] -->|"Syntax"| F["() => 'Hello'"]
    G["Implicit Return"] -->|"Syntax"| H["(a) => a * 2"]

Example: Arrow Function vs. Regular Function

// Regular function (has `this` and `arguments`)
function regularFunc() {
    console.log(this); // Window (global) or object context
    console.log(arguments); // [1, 2, 3]
}

// Arrow function (lexical `this`)
const arrowFunc = () => {
    console.log(this); // Inherits `this` from parent scope
    // console.log(arguments); // ReferenceError
};

Trace: this Binding in Objects

const obj = {
    name: "Daraz",
    greet: function() {
        console.log(`Hello, ${this.name}!`);
    },
    greetArrow: () => {
        console.log(`Hello, ${this.name}!`); // `this` is Window (or undefined in strict mode)
    }
};
obj.greet();      // "Hello, Daraz!"
obj.greetArrow(); // Error (or "Hello, undefined!")

5. Built-in Array Methods

JavaScript arrays have powerful methods for iteration, transformation, and aggregation without mutating the original array.

5.1 Common Array Methods

Method Purpose Example Mutates Original?
map Transform each element [1, 2].map(x => x * 2) → [2, 4] ❌ No
filter Return elements meeting condition [1, 2, 3].filter(x => x > 1) → [2, 3] ❌ No
reduce Aggregate values into one result [1, 2, 3].reduce((a, b) => a + b, 0) → 6 ❌ No
forEach Execute a function for each item [1, 2].forEach(console.log) ❌ No
find Return first matching element [1, 2, 3].find(x => x > 1) → 2 ❌ No

5.2 Real-World Use: Daraz Order Processing

Daraz uses reduce to calculate total order value from items:

const orderItems = [
    { product: "Laptop", price: 50000, quantity: 1 },
    { product: "Mouse", price: 500, quantity: 2 }
];

const total = orderItems.reduce((sum, item) => sum + (item.price * item.quantity), 0);
console.log(`Total: Rs. ${total}`); // Rs. 50500

Trace: reduce Execution

Step sum (accumulator) item (current) sum + (item.price * item.quantity) Result
1 0 (initial) { product: "Laptop", price: 50000, quantity: 1 } 0 + (50000 * 1) = 50000 50000
2 50000 { product: "Mouse", price: 500, quantity: 2 } 50000 + (500 * 2) = 50500 50500

5.3 map vs. forEach

Feature map forEach
Purpose Transform and return new array Execute side effects
Return New array undefined
Use Case Data transformation Logging, DOM updates
Example [1, 2].map(x => x * 2) → [2, 4] [1, 2].forEach(console.log)

Example: Converting Ncell Users to Objects

const userIds = ["user1", "user2", "user3"];

// Using map (returns new array)
const users = userIds.map(id => ({ id, status: "active" }));
console.log(users);
// Output: [{ id: "user1", status: "active" }, ...]

// Using forEach (no return)
userIds.forEach(id => console.log(`Processing ${id}`));
// Output: "Processing user1", "Processing user2", etc.

6. Recursion: Solving Problems with Self-Calls

Recursion is a technique where a function calls itself to solve smaller instances of the same problem.

6.1 Recursive Factorial Example

flowchart TD
    A["factorial(5)"] -->|"Returns"| B["5 * factorial(4)"]
    B -->|"Returns"| C["4 * factorial(3)"]
    C -->|"Returns"| D["3 * factorial(2)"]
    D -->|"Returns"| E["2 * factorial(1)"]
    E -->|"Returns"| F["1 * factorial(0)"]
    F -->|"Base Case"| G["1"]
    G -->|"Unwind"| E["2 * 1 = 2"]
    E -->|"Unwind"| D["3 * 2 = 6"]
    D -->|"Unwind"| C["4 * 6 = 24"]
    C -->|"Unwind"| B["5 * 24 = 120"]
    B -->|"Final"| A["120"]

Code: Recursive Factorial

function factorial(n) {
    if (n === 0) return 1; // Base case
    return n * factorial(n - 1); // Recursive case
}

console.log(factorial(5)); // 120

Trace: factorial(3) Execution

Step Call Stack Return Value Result
1 factorial(3) 3 * ? -
2 factorial(2) 2 * ? -
3 factorial(1) 1 * ? -
4 factorial(0) → 1 1 Unwind
5 1 * 1 = 1 1 Unwind
6 2 * 1 = 2 2 Unwind
7 3 * 2 = 6 6 Output

6.2 Real-World Use: Ncell Call Tree

Ncell uses recursion to flatten nested call logs for billing:

function flattenCalls(callLog) {
    let result = [];
    for (const call of callLog) {
        if (Array.isArray(call)) {
            result.push(...flattenCalls(call)); // Recursive flatten
        } else {
            result.push(call);
        }
    }
    return result;
}

const nestedCalls = ["call1", ["call2", ["call3"]]];
console.log(flattenCalls(nestedCalls)); // ["call1", "call2", "call3"]

7. Method Overriding in JavaScript (Prototype Chaining)

JavaScript uses prototype-based inheritance, where child objects can override parent methods.

7.1 Prototype Chain Visualization

classDiagram
    class Animal {
        +speak() "Makes a sound"
    }
    class Dog {
        +speak() "Barks!"
    }
    Animal <|-- Dog : Inherits

Example: Overriding speak()

class Animal {
    speak() {
        return "Some sound";
    }
}

class Dog extends Animal {
    speak() { // Overrides parent method
        return "Bark!";
    }
}

const dog = new Dog();
console.log(dog.speak()); // "Bark!"

Trace: dog.speak() Execution

Step Method Lookup Result
1 Check Dog.prototype Finds speak() → "Bark!"
2 (No need to check Animal) -

In the Real World

  1. eSewa Payment API

    • Closures hide API keys from global scope, preventing exposure in client-side code.
    • Example: The secureAPICall function (Section 3.2) ensures sensitive data is never leaked.
  2. Daraz Order Processing

    • reduce method calculates total order value dynamically (Section 5.2).
    • Example: Summing price * quantity for all items in a cart.
  3. Ncell Call Logs

    • Recursion flattens nested call logs for billing reports (Section 6.2).
    • Example: Converting ["call1", ["call2", ["call3"]]] into ["call1", "call2", "call3"].
  4. Khalti Transaction Validation

    • Arrow functions are used in event handlers for concise syntax (e.g., button clicks).
    • Example:
      document.getElementById("payBtn").addEventListener("click", () => {
          validatePayment(); // Arrow function preserves `this`
      });
      
  5. NEPSE Stock Data Aggregation

    • map and filter transform raw stock data into readable formats.
    • Example:
      const stocks = [{ symbol: "NTC", price: 100 }, { symbol: "NEPSE", price: 200 }];
      const highValueStocks = stocks.filter(s => s.price > 150).map(s => s.symbol);
      // ["NEPSE"]
      

Exam Tip

  1. Function Questions

    • Always declare functions before calling them if using declarations (hoisting).
    • For sum() with arbitrary parameters, use rest parameters (...args).
    • Example answer for past exam:
      function sum(...numbers) {
          return numbers.reduce((total, num) => total + num, 0);
      }
      console.log(sum(1, 2)); // 3
      
  2. Scope and Closures

    • Use let/const for block scope to avoid hoisting issues.
    • Closures are tested via data privacy or function factories. Memorize the createCounter pattern.
  3. Array Methods

    • map → Transform → Returns new array.
    • filter → Select → Returns filtered array.
    • reduce → Aggregate → Returns single value.
    • forEach → Side effects → Returns undefined.
    • Never mutate the original array in exams unless asked.
  4. Recursion

    • Always include a base case to terminate recursion.
    • Trace the call stack step-by-step (like the factorial example).
  5. Arrow Functions

    • Use them for concise callbacks but avoid in object methods needing this.
    • Example pitfall:
      const obj = {
          name: "Pathao",
          greet: () => console.log(this.name) // ❌ `this` is Window!
      };
      
  6. Prototype Overriding

    • Child classes override parent methods by redefining them.
    • Use extends and super() if needed (though TU syllabus focuses on basic overriding).

Pro Tip for TU Exams:

  • Draw diagrams for scope/closure questions (e.g., lexical environment boxes).
  • Trace variable values in tables for recursion or reduce operations.
  • Compare methods in tables (e.g., map vs. forEach) to show differences clearly.

Based on the TU BCA syllabus for Scripting Language (CACS254), unit 3.

Discussion

Loading…