|

JavaScript 28 🧬 Encapsulation

Encapsulation is one of the fundamental concepts in object-oriented programming (OOP). It’s used to restrict access to some of an object’s internal state — protecting data integrity, managing complexity, and promoting maintainability by allowing controlled access to internal properties and behaviors.


A Quick Look at the Examples

// 1. With closures
function Counter() {
    let count = 0;

    return {
        increment: function () { count++; },
        decrement: function () { count--; },
        getCount: function () { return count; }
    };
}

const counter = Counter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
console.log(counter.count);      // undefined (private!)

// 2. With ES6 classes and private fields
class BankAccount {
    #balance = 0;

    constructor(initialBalance) {
        this.#balance = initialBalance;
    }

    deposit(amount) {
        if (amount < 0) throw new Error("Cannot deposit negative amount");
        this.#balance += amount;
    }

    withdraw(amount) {
        if (amount > this.#balance) throw new Error("Insufficient balance");
        this.#balance -= amount;
    }

    getBalance() {
        return this.#balance;
    }
}

const account = new BankAccount(1000);
account.deposit(500);
account.withdraw(200);
console.log(account.getBalance()); // 1300
// console.log(account.#balance);  // ❌ SyntaxError: private field

// 3. With getters and setters
class Person {
    #name;

    constructor(name) {
        this.#name = name;
    }

    get name() {
        return this.#name;
    }

    set name(newName) {
        if (newName.length < 2) {
            throw new Error("Name must be at least 2 characters");
        }
        this.#name = newName;
    }
}

const person = new Person("Alice");
console.log(person.name); // "Alice"
// person.name = "A";      // ❌ Error: Name must be at least 2 characters

// 4. With private module scope
let count = 0; // Not exported — private!

export function increment() { count++; }
export function decrement() { count--; }
export function getCount()  { return count; }

// 5. With IIFE (Immediately Invoked Function Expression)
const Counter2 = (function () {
    let count = 0;

    function increment() { count++; }
    function decrement() { count--; }
    function getCount()  { return count; }

    return { increment, decrement, getCount };
})();

Counter2.increment();
Counter2.increment();
console.log(Counter2.getCount()); // 2
console.log(Counter2.count);      // undefined (private!)

a. What is Encapsulation?

Encapsulation is the practice of restricting direct access to an object’s internal state. It allows controlled access through methods, protecting data from unintended modification.

Core Concepts

ConceptDescription
Data HidingHide the internal state (variables) to prevent unintended modifications
Access ControlProvide controlled access (via methods) to the internal state
AbstractionExpose only the necessary parts of the object’s behavior, hiding implementation details

Why Encapsulation Matters

BenefitDescription
SecurityPrevents unauthorized changes to internal state
MaintainabilityMakes code easier to modify and debug
ReusabilityEncapsulated code is more modular and reusable
ScalabilityReduces dependencies and makes large systems manageable

b. How to Achieve Encapsulation — Part 1

1. Using Closures (Function Scopes)

Closures allow you to create private variables and functions that are not accessible outside the scope of the function.

function Counter() {
    let count = 0; // Private — inaccessible from outside

    return {
        increment: function () { count++; },
        decrement: function () { count--; },
        getCount: function () { return count; }
    };
}

const counter = Counter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
console.log(counter.count);      // undefined — private!

Advantages:

  • ✅ Strong data privacy
  • ✅ Prevents direct tampering with internal state

How it works:

Counter()
    │
    ▼
┌─────────────────────┐
│ let count = 0;      │ ← Private variable
│                     │
│ return {            │
│   increment,        │ ← Public methods
│   decrement,        │   (closures over count)
│   getCount          │
│ }                   │
└─────────────────────┘

2. Using ES6 Classes with Private Fields (#)

ES6 introduced private class fields, denoted by #, which cannot be accessed from outside the class.

class BankAccount {
    #balance = 0; // Private field

    constructor(initialBalance) {
        this.#balance = initialBalance;
    }

    deposit(amount) {
        if (amount < 0) throw new Error("Cannot deposit negative amount");
        this.#balance += amount;
    }

    withdraw(amount) {
        if (amount > this.#balance) throw new Error("Insufficient balance");
        this.#balance -= amount;
    }

    getBalance() {
        return this.#balance;
    }
}

const account = new BankAccount(1000);
account.deposit(500);
account.withdraw(200);
console.log(account.getBalance()); // 1300

// console.log(account.#balance);  // ❌ SyntaxError

Advantages:

  • ✅ Native support for private fields
  • ✅ Clean, readable, modern syntax
  • ✅ Enforced by the language (true privacy)

3. Using ES6 Classes with Getters/Setters

Use get and set methods to control access to internal state.

class Person {
    #name;

    constructor(name) {
        this.#name = name;
    }

    get name() {
        return this.#name;
    }

    set name(newName) {
        if (newName.length < 2) {
            throw new Error("Name must be at least 2 characters");
        }
        this.#name = newName;
    }
}

const person = new Person("Alice");
console.log(person.name); // "Alice" — uses getter

person.name = "Bob";      // Uses setter (valid)
console.log(person.name); // "Bob"

// person.name = "A";     // ❌ Error: Name must be at least 2 characters

Advantages:

  • ✅ Enforces validation and constraints
  • ✅ Encapsulates logic for reading and writing data
  • ✅ Same syntax as regular properties

c. How to Achieve Encapsulation — Part 2

4. Using ES6 Modules (Private Module Scope)

Modules allow you to expose only certain parts of your code while keeping the rest private.

// counter.js
let count = 0; // Not exported — private!

export function increment() { count++; }
export function decrement() { count--; }
export function getCount()  { return count; }
// main.js
import { increment, getCount } from './counter.js';

increment();
increment();
console.log(getCount()); // 2

// console.log(count);     // ❌ ReferenceError: count is not defined

Advantages:

  • ✅ Clean separation of public and private code
  • ✅ Encourages modular and reusable code

5. Using IIFE (Immediately Invoked Function Expression)

IIFEs create a private scope and expose only the necessary methods.

const Counter = (function () {
    let count = 0; // Private

    function increment() { count++; }
    function decrement() { count--; }
    function getCount()  { return count; }

    return {
        increment,
        decrement,
        getCount
    };
})();

Counter.increment();
Counter.increment();
console.log(Counter.getCount()); // 2
console.log(Counter.count);      // undefined — private!

Advantages:

  • ✅ Works in older JavaScript environments (pre-ES6)
  • ✅ Encapsulates state and logic in a single scope

How it works:

(function () {
    let count = 0;       // Private scope
    return { ... };      // Only these are exposed
})();
     │
     ▼
Immediately invoked — runs once, returns the public API

When to Use Which Approach

ApproachBest ForNotes
ClosuresSmall, standalone utilitiesNot suitable for large-scale OOP
ES6 Classes (private #)Modern, scalable appsSupports class-based OOP
Getters/SettersFine-grained data controlEnhances readability and safety
ES6 ModulesModular applicationsIdeal for large projects
IIFELegacy environments or librariesLess readable than ES6 classes

Comparison of Approaches

ApproachTrue Privacy?Modern?ComplexityBest For
Closures✅ Yes✅ YesLowSmall utilities, factories
ES6 # Private✅ Yes (enforced)✅ YesLowModern OOP
Getters/Setters❌ (with # yes)✅ YesLowValidation, computed props
Modules✅ Yes✅ YesLowMulti-file apps
IIFE✅ Yes⚠️ LegacyMediumPre-ES6 code, libraries

Complete Example

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Encapsulation</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background: #f8f9fa;
            color: #333;
            line-height: 1.6;
        }
        h1 { color: #007bff; border-bottom: 3px solid #007bff; padding-bottom: 10px; }
        h2 { color: #28a745; border-left: 4px solid #28a745; padding-left: 15px; margin-top: 30px; }
        .demo-box {
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            margin: 15px 0;
        }
        pre {
            background: #1e1e1e;
            color: #d4d4d4;
            padding: 15px;
            border-radius: 8px;
            overflow-x: auto;
            font-family: 'Courier New', monospace;
            font-size: 0.9rem;
            line-height: 1.8;
        }
        .keyword { color: #569cd6; }
        .string { color: #ce9178; }
        .number { color: #b5cea8; }
        .function { color: #dcdcaa; }
        .comment { color: #6a9955; }
        .boolean { color: #569cd6; }
        .private { color: #c586c0; }
        #output {
            background: #e9ecef;
            padding: 15px;
            border-radius: 8px;
            margin-top: 15px;
            min-height: 40px;
            font-family: 'Courier New', monospace;
            font-size: 0.85rem;
            border-left: 4px solid #007bff;
            white-space: pre-wrap;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin: 15px 0;
        }
        th, td {
            padding: 10px;
            border: 1px solid #ddd;
            text-align: left;
        }
        th { background: #007bff; color: white; }
        tr:nth-child(even) { background: #f8f9fa; }
        .btn {
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 6px;
            font-size: 1em;
            font-weight: bold;
            cursor: pointer;
            margin: 5px;
            transition: all 0.3s;
        }
        .btn:hover {
            background: #0056b3;
            transform: translateY(-2px);
        }
        .btn-success { background: #28a745; }
        .btn-success:hover { background: #1e7e34; }
        .btn-danger { background: #dc3545; }
        .btn-danger:hover { background: #a71d2a; }
        .balance-display {
            font-size: 2.5em;
            font-weight: bold;
            color: #28a745;
            text-align: center;
            padding: 20px;
            font-family: 'Courier New', monospace;
            background: #f8f9fa;
            border-radius: 8px;
            margin: 15px 0;
        }
        .input-group {
            margin: 10px 0;
        }
        .input-group label {
            display: inline-block;
            min-width: 150px;
            font-weight: bold;
        }
        .input-group input {
            padding: 8px 12px;
            border: 2px solid #ddd;
            border-radius: 6px;
            font-size: 1em;
            width: 150px;
        }
        .input-group input:focus {
            outline: none;
            border-color: #007bff;
        }
        .log-display {
            background: #1e1e1e;
            color: #d4d4d4;
            padding: 15px;
            border-radius: 8px;
            font-family: 'Courier New', monospace;
            font-size: 0.85rem;
            max-height: 200px;
            overflow-y: auto;
            margin: 10px 0;
        }
        .log-entry { margin: 3px 0; }
        .log-success { color: #4ec9b0; }
        .log-error { color: #f48771; }
        .log-info { color: #569cd6; }
    </style>
</head>
<body>

    <h1>Encapsulation</h1>

    <div class="demo-box">
        <h2>1. With Closures</h2>
        <pre>
<span class="keyword">function</span> <span class="function">Counter</span>() {
    <span class="keyword">let</span> count = <span class="number">0</span>; <span class="comment">// Private!</span>

    <span class="keyword">return</span> {
        increment: <span class="keyword">function</span> () { count++; },
        decrement: <span class="keyword">function</span> () { count--; },
        getCount: <span class="keyword">function</span> () { <span class="keyword">return</span> count; }
    };
}

<span class="keyword">const</span> counter = <span class="function">Counter</span>();
counter.<span class="function">increment</span>();
counter.<span class="function">increment</span>();
console.log(counter.<span class="function">getCount</span>()); <span class="comment">// 2</span>
console.log(counter.count);      <span class="comment">// undefined (private!)</span>
        </pre>
    </div>

    <div class="demo-box">
        <h2>2. With ES6 Private Fields (#)</h2>
        <pre>
<span class="keyword">class</span> <span class="function">BankAccount</span> {
    <span class="private">#balance</span> = <span class="number">0</span>; <span class="comment">// Private field</span>

    <span class="function">constructor</span>(initialBalance) {
        <span class="keyword">this</span>.<span class="private">#balance</span> = initialBalance;
    }

    <span class="function">deposit</span>(amount) {
        <span class="keyword">if</span> (amount < <span class="number">0</span>) <span class="keyword">throw new</span> <span class="function">Error</span>(<span class="string">"Cannot deposit negative amount"</span>);
        <span class="keyword">this</span>.<span class="private">#balance</span> += amount;
    }

    <span class="function">withdraw</span>(amount) {
        <span class="keyword">if</span> (amount > <span class="keyword">this</span>.<span class="private">#balance</span>) <span class="keyword">throw new</span> <span class="function">Error</span>(<span class="string">"Insufficient balance"</span>);
        <span class="keyword">this</span>.<span class="private">#balance</span> -= amount;
    }

    <span class="function">getBalance</span>() {
        <span class="keyword">return</span> <span class="keyword">this</span>.<span class="private">#balance</span>;
    }
}
        </pre>
    </div>

    <div class="demo-box">
        <h2>3. With Getters and Setters</h2>
        <pre>
<span class="keyword">class</span> <span class="function">Person</span> {
    <span class="private">#name</span>;

    <span class="function">constructor</span>(name) {
        <span class="keyword">this</span>.<span class="private">#name</span> = name;
    }

    <span class="keyword">get</span> <span class="function">name</span>() {
        <span class="keyword">return</span> <span class="keyword">this</span>.<span class="private">#name</span>;
    }

    <span class="keyword">set</span> <span class="function">name</span>(newName) {
        <span class="keyword">if</span> (newName.length < <span class="number">2</span>) {
            <span class="keyword">throw new</span> <span class="function">Error</span>(<span class="string">"Name must be at least 2 characters"</span>);
        }
        <span class="keyword">this</span>.<span class="private">#name</span> = newName;
    }
}
        </pre>
    </div>

    <div class="demo-box">
        <h2>4. Comparison of Approaches</h2>
        <table>
            <tr>
                <th>Approach</th>
                <th>True Privacy?</th>
                <th>Modern?</th>
                <th>Best For</th>
            </tr>
            <tr>
                <td><strong>Closures</strong></td>
                <td>✅ Yes</td>
                <td>✅ Yes</td>
                <td>Small utilities, factories</td>
            </tr>
            <tr>
                <td><strong>ES6 # Private</strong></td>
                <td>✅ Yes (enforced)</td>
                <td>✅ Yes</td>
                <td>Modern OOP</td>
            </tr>
            <tr>
                <td><strong>Getters/Setters</strong></td>
                <td>⚠️ Partial</td>
                <td>✅ Yes</td>
                <td>Validation, computed props</td>
            </tr>
            <tr>
                <td><strong>Modules</strong></td>
                <td>✅ Yes</td>
                <td>✅ Yes</td>
                <td>Multi-file apps</td>
            </tr>
            <tr>
                <td><strong>IIFE</strong></td>
                <td>✅ Yes</td>
                <td>⚠️ Legacy</td>
                <td>Pre-ES6 code, libraries</td>
            </tr>
        </table>
    </div>

    <div class="demo-box">
        <h2>5. Interactive: Bank Account</h2>
        <p>A bank account with encapsulated balance — you can only interact through methods.</p>
        <div class="balance-display" id="balanceDisplay">$0</div>
        <div class="input-group">
            <label for="amountInput">Amount:</label>
            <input type="number" id="amountInput" value="100" step="50">
        </div>
        <div style="text-align: center;">
            <button class="btn btn-success" onclick="deposit()">Deposit</button>
            <button class="btn btn-danger" onclick="withdraw()">Withdraw</button>
            <button class="btn" onclick="showBalance()">Check Balance</button>
            <button class="btn" onclick="tryDirectAccess()">Try account.#balance</button>
        </div>
        <div class="log-display" id="logDisplay"></div>
    </div>

    <div class="demo-box">
        <h2>6. Interactive: Counter</h2>
        <p>A counter using closures — the count is private.</p>
        <div class="balance-display" id="counterDisplay">0</div>
        <div style="text-align: center;">
            <button class="btn btn-success" onclick="counterIncrement()">+ Increment</button>
            <button class="btn btn-danger" onclick="counterDecrement()">− Decrement</button>
            <button class="btn" onclick="tryCounterAccess()">Try counter.count</button>
        </div>
        <div class="log-display" id="counterLog"></div>
    </div>

    <div class="demo-box">
        <h2>7. Live Output — All Examples</h2>
        <div id="output">Loading...</div>
    </div>

    <script>
        // ============================================
        // Encapsulation — Live Demo
        // ============================================

        let results = [];

        // 1. Closures
        results.push('📌 Encapsulation with Closures:\n');

        function Counter() {
            let count = 0;

            return {
                increment: function () { count++; },
                decrement: function () { count--; },
                getCount: function () { return count; }
            };
        }

        const counter = Counter();
        counter.increment();
        counter.increment();
        counter.increment();
        results.push('  counter.increment() × 3');
        results.push('  counter.getCount() → ' + counter.getCount());
        results.push('  counter.count → ' + counter.count + ' (undefined — private!)');
        results.push('');

        // 2. ES6 Private Fields
        results.push('📌 Encapsulation with ES6 Private Fields:\n');

        class BankAccount {
            #balance = 0;

            constructor(initialBalance) {
                this.#balance = initialBalance;
            }

            deposit(amount) {
                if (amount < 0) throw new Error("Cannot deposit negative amount");
                this.#balance += amount;
                return this.#balance;
            }

            withdraw(amount) {
                if (amount > this.#balance) throw new Error("Insufficient balance");
                this.#balance -= amount;
                return this.#balance;
            }

            getBalance() {
                return this.#balance;
            }
        }

        const account = new BankAccount(1000);
        results.push('  new BankAccount(1000)');
        results.push('  account.getBalance() → $' + account.getBalance());

        account.deposit(500);
        results.push('  account.deposit(500) → $' + account.getBalance());

        account.withdraw(200);
        results.push('  account.withdraw(200) → $' + account.getBalance());

        try {
            account.withdraw(5000);
        } catch (error) {
            results.push('  account.withdraw(5000) → ❌ ' + error.message);
        }

        try {
            account.deposit(-100);
        } catch (error) {
            results.push('  account.deposit(-100) → ❌ ' + error.message);
        }
        results.push('');

        // 3. Getters and Setters
        results.push('📌 Encapsulation with Getters/Setters:\n');

        class Person {
            #name;

            constructor(name) {
                this.#name = name;
            }

            get name() {
                return this.#name;
            }

            set name(newName) {
                if (newName.length < 2) {
                    throw new Error("Name must be at least 2 characters");
                }
                this.#name = newName;
            }
        }

        const person = new Person("Alice");
        results.push('  new Person("Alice") → name = "' + person.name + '"');

        person.name = "Bob";
        results.push('  person.name = "Bob" → name = "' + person.name + '"');

        try {
            person.name = "A";
        } catch (error) {
            results.push('  person.name = "A" → ❌ ' + error.message);
        }
        results.push('');

        // 4. Module pattern (simulated)
        results.push('📌 Encapsulation with Module Pattern:\n');

        const CounterModule = (function () {
            let count = 0; // Private

            return {
                increment: function () { count++; return count; },
                decrement: function () { count--; return count; },
                getCount: function () { return count; }
            };
        })();

        results.push('  CounterModule.increment() → ' + CounterModule.increment());
        results.push('  CounterModule.increment() → ' + CounterModule.increment());
        results.push('  CounterModule.getCount() → ' + CounterModule.getCount());
        results.push('  CounterModule.count → ' + CounterModule.count + ' (private!)');
        results.push('');

        // 5. IIFE
        results.push('📌 Encapsulation with IIFE:\n');

        const CounterIIFE = (function () {
            let count = 0;

            function increment() { count++; }
            function decrement() { count--; }
            function getCount()  { return count; }

            return { increment, decrement, getCount };
        })();

        CounterIIFE.increment();
        CounterIIFE.increment();
        CounterIIFE.increment();
        results.push('  CounterIIFE.increment() × 3');
        results.push('  CounterIIFE.getCount() → ' + CounterIIFE.getCount());
        results.push('  CounterIIFE.count → ' + CounterIIFE.count + ' (private!)');
        results.push('');

        // 6. Practical: Temperature with validation
        results.push('📌 Practical: Temperature with Validation:\n');

        class Temperature {
            #celsius = 0;

            constructor(celsius) {
                this.celsius = celsius; // Uses setter
            }

            get celsius() {
                return this.#celsius;
            }

            set celsius(value) {
                if (value < -273.15) {
                    throw new Error("Temperature below absolute zero!");
                }
                this.#celsius = value;
            }

            get fahrenheit() {
                return this.#celsius * 9 / 5 + 32;
            }

            set fahrenheit(value) {
                this.celsius = (value - 32) * 5 / 9;
            }
        }

        const temp = new Temperature(25);
        results.push('  new Temperature(25)');
        results.push('  temp.celsius → ' + temp.celsius + '°C');
        results.push('  temp.fahrenheit → ' + temp.fahrenheit + '°F');

        temp.fahrenheit = 100;
        results.push('  temp.fahrenheit = 100 → celsius = ' + temp.celsius.toFixed(2) + '°C');

        try {
            temp.celsius = -300;
        } catch (error) {
            results.push('  temp.celsius = -300 → ❌ ' + error.message);
        }

        document.getElementById('output').textContent = results.join('\n');

        // ============================================
        // Interactive: Bank Account
        // ============================================

        class InteractiveBank {
            #balance = 0;

            constructor(initial) {
                this.#balance = initial;
            }

            deposit(amount) {
                if (amount < 0) throw new Error("Cannot deposit negative amount");
                if (amount === 0) throw new Error("Deposit amount must be positive");
                this.#balance += amount;
                return this.#balance;
            }

            withdraw(amount) {
                if (amount <= 0) throw new Error("Withdrawal must be positive");
                if (amount > this.#balance) throw new Error("Insufficient balance");
                this.#balance -= amount;
                return this.#balance;
            }

            getBalance() {
                return this.#balance;
            }
        }

        const bankAccount = new InteractiveBank(1000);

        function logTo(containerId, message, type = 'info') {
            const container = document.getElementById(containerId);
            const entry = document.createElement('div');
            entry.className = 'log-entry log-' + type;
            entry.textContent = '> ' + message;
            container.appendChild(entry);
            container.scrollTop = container.scrollHeight;
        }

        function updateBalance() {
            document.getElementById('balanceDisplay').textContent =
                '$' + bankAccount.getBalance().toLocaleString();
        }

        function deposit() {
            const amount = Number(document.getElementById('amountInput').value);
            try {
                const balance = bankAccount.deposit(amount);
                updateBalance();
                logTo('logDisplay', `Deposited $${amount}. New balance: $${balance}`, 'success');
            } catch (error) {
                logTo('logDisplay', error.message, 'error');
            }
        }

        function withdraw() {
            const amount = Number(document.getElementById('amountInput').value);
            try {
                const balance = bankAccount.withdraw(amount);
                updateBalance();
                logTo('logDisplay', `Withdrew $${amount}. New balance: $${balance}`, 'success');
            } catch (error) {
                logTo('logDisplay', error.message, 'error');
            }
        }

        function showBalance() {
            const balance = bankAccount.getBalance();
            logTo('logDisplay', `Balance: $${balance}`, 'info');
        }

        function tryDirectAccess() {
            logTo('logDisplay', 'Attempting: account.#balance', 'info');
            try {
                // This will fail at parse time in real code — simulated here
                logTo('logDisplay', '❌ SyntaxError: Private field #balance must be declared in an enclosing class', 'error');
            } catch (error) {
                logTo('logDisplay', error.message, 'error');
            }
        }

        // ============================================
        // Interactive: Counter
        // ============================================

        const interactiveCounter = (function () {
            let count = 0;
            return {
                increment: () => ++count,
                decrement: () => --count,
                getCount: () => count
            };
        })();

        function updateCounterDisplay() {
            document.getElementById('counterDisplay').textContent = interactiveCounter.getCount();
        }

        function counterIncrement() {
            const val = interactiveCounter.increment();
            updateCounterDisplay();
            logTo('counterLog', `Incremented → ${val}`, 'success');
        }

        function counterDecrement() {
            const val = interactiveCounter.decrement();
            updateCounterDisplay();
            logTo('counterLog', `Decremented → ${val}`, 'success');
        }

        function tryCounterAccess() {
            logTo('counterLog', 'Attempting: counter.count', 'info');
            logTo('counterLog', '→ undefined (count is private via closure!)', 'error');
        }

        // Initialize
        updateBalance();
        logTo('logDisplay', 'Bank account initialized with $1000', 'info');
        logTo('counterLog', 'Counter initialized with 0', 'info');
    </script>

</body>
</html>

Quick Reference

Core Concepts

ConceptDescription
Data HidingHide internal state to prevent unintended modifications
Access ControlProvide controlled access via methods
AbstractionExpose only necessary behavior

Why Encapsulation Matters

BenefitDescription
SecurityPrevents unauthorized changes
MaintainabilityEasier to modify and debug
ReusabilityMore modular and reusable
ScalabilityReduces dependencies

Approaches Comparison

ApproachTrue Privacy?Modern?Best For
Closures✅ Yes✅ YesSmall utilities, factories
ES6 # Private✅ Yes (enforced)✅ YesModern OOP
Getters/Setters⚠️ Partial✅ YesValidation, computed props
Modules✅ Yes✅ YesMulti-file apps
IIFE✅ Yes⚠️ LegacyPre-ES6 code, libraries

Best Practices

Do This:

// Use ES6 private fields for modern OOP
class User {
    #password;
    constructor(password) {
        this.#password = password;
    }
    checkPassword(input) {
        return input === this.#password;
    }
}

// Use getters/setters for validation
class Person {
    #name;
    set name(value) {
        if (typeof value !== 'string') throw new Error('Invalid name');
        this.#name = value;
    }
}

// Use closures for factory functions
const createCounter = () => {
    let count = 0;
    return {
        increment: () => ++count,
        getCount: () => count
    };
};

// Use modules for large apps
// utils.js
let secret = 'private';
export const getSecret = () => secret;

Don’t Do This:

// Don't use underscores for "privacy"
class User {
    constructor() {
        this._password = 'secret'; // ⚠️ Convention only — still accessible!
    }
}
user._password; // Works! Not truly private

// Don't expose internal state directly
class BadCounter {
    constructor() {
        this.count = 0; // ❌ Public — anyone can change!
    }
}
counter.count = -999; // Broken!

// Don't skip validation in setters
class BadPerson {
    set age(value) {
        this._age = value; // ❌ No validation!
    }
}
person.age = -5; // Invalid age accepted!

// Don't confuse getters with methods
class BadExample {
    getValue() { return this.#value; } // Method
    get value() { return this.#value; } // Getter
}

Common Pitfalls

PitfallProblemSolution
_underscore conventionNot truly privateUse # or closures
Forgetting validationInvalid stateValidate in setters
Exposing referencesInternal objects mutatedReturn copies
Over-encapsulatingToo many gettersExpose meaningful operations
# outside classSyntaxErrorOnly inside class body

Real-World Examples

// 1. React component with private state (via hooks)
function Counter() {
    const [count, setCount] = useState(0); // Encapsulated
    return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

// 2. Node.js module
// db.js
let connection; // Private
export function connect() { connection = createConnection(); }
export function query(sql) { return connection.query(sql); }

// 3. Service class
class ApiService {
    #baseUrl;
    #token;

    constructor(baseUrl, token) {
        this.#baseUrl = baseUrl;
        this.#token = token;
    }

    async get(endpoint) {
        const response = await fetch(`${this.#baseUrl}${endpoint}`, {
            headers: { Authorization: `Bearer ${this.#token}` }
        });
        return response.json();
    }
}

Pro Tip: Encapsulation is about protecting your data and hiding implementation details. Use ES6 private fields (#) for modern OOP — they’re enforced by the language. Use closures for factory functions and small modules. Use getters/setters when you need validation or computed properties. Use modules for multi-file applications. Remember: the _underscore convention is just a convention — it doesn’t provide true privacy. For real encapsulation, use # private fields (modern) or closures (universal)!


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!