Sitemap
Riki Graha

Riki Graha

🔹 Building connected learning journeys ✨ ✍️ From web fundamentals to n8n automation ⚙️ and practical Medium growth 📊 — HTML, CSS, JS, React, Next.js 🚀

Basic of JavaScript — Chapter 7 [End]

12 min readMay 17, 2025

--

Learn HTML, CSS, and JavaScript Programming from Beginner to Expert

Press enter or click to view image in full size

Welcome back to the final chapter of our JavaScript Basics series ! 🎉

You’ve come a long way since Chapter 1. From understanding variables and data types to mastering loops, functions, and advanced control flow, you’re now ready to tackle some of the most powerful and nuanced features of JavaScript.

In this final chapter, we’ll explore:

  • Closure : A powerful concept for encapsulating logic
  • Object Methods : Adding functionality to objects
  • The this Keyword : Understanding how this works in different contexts
  • Arrow Functions in Objects : Why arrow functions behave differently with this
  • Getter and Setter : Managing object properties dynamically
  • Problems with var : Why let and const are better alternatives
  • Destructuring : Simplifying access to complex data structures
  • Strict Mode : Writing safer and more predictable code
  • Debugger : Using built-in tools to step through your code

Each section includes real-life examples , hands-on practice , and clear explanations so you can confidently apply these concepts to your own projects.

Let’s dive in and wrap up this journey with a bang!

Closure

A closure is a function that retains access to its outer scope, even after the outer function has finished executing. This allows closures to “remember” variables from their enclosing context, making them incredibly useful for tasks like creating private variables or managing state.

Here’s the basic idea:

  • A closure is created when a function returns another function.
  • The inner function retains access to variables from the outer function.

Let’s see how closures work with real-world examples.

Example 1: Creating a Counter

You can use closures to create a private counter.

function createCounter() {
let count = 0;

return function() {
count++;
return count;
};
}

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

✅ What Happens?
The createCounter function returns a closure that increments and remembers the count variable — perfect for state management , private variables , and advanced patterns .

Example 2: Simulating Private Variables

Closures can be used to simulate private variables in JavaScript.

function createBankAccount(initialBalance) {
let balance = initialBalance;

return {
deposit(amount) {
balance += amount;
return balance;
},
withdraw(amount) {
if (amount <= balance) {
balance -= amount;
return balance;
}
return "Insufficient funds";
}
};
}

let account = createBankAccount(100);
console.log(account.deposit(50)); // 150
console.log(account.withdraw(30)); // 120
console.log(account.withdraw(200)); // Insufficient funds

✅ What Happens?
This creates a bank account with private balance that can only be modified through deposit and withdraw methods — widely used in encapsulation , data hiding , and module design .

Example 3: Event Handlers with Closures

Closures are often used in event handlers to capture dynamic values.

function createButtons() {
for (let i = 1; i <= 3; i++) {
let button = document.createElement("button");
button.innerText = `Button ${i}`;
button.onclick = function() {
alert(`You clicked Button ${i}`);
};
document.body.appendChild(button);
}
}

createButtons();

✅ What Happens?
Each button “remembers” its index (i) thanks to closures — ideal for dynamic UIs , event handling , and asynchronous workflows .

Summary

In this section, you’ve learned how closures allow functions to retain access to their outer scope.

You explored:

  • How to create a private counter
  • How to simulate private variables
  • How to use closures in event handlers

Closures are powerful tools for state management , data encapsulation , and dynamic behavior .

Next, let’s explore object methods , which add functionality directly to objects.

Object Method

An object method is simply a function stored as a property of an object. These methods allow objects to perform actions or calculations related to their data.

Here’s the basic structure:

let object = {
methodName: function() {
// Code to execute
}
};

Let’s see how object methods work with real examples.

Example 1: Greeting Users

You can add a method to greet users dynamically.

let user = {
name: "Alice",
greet: function() {
console.log(`Hello, ${this.name}!`);
}
};

user.greet(); // Hello, Alice!

✅ What Happens?
The greet method uses the name property of the object — demonstrating how methods interact with object data.

Example 2: Calculating Area

You can add a method to calculate the area of a rectangle.

let rectangle = {
width: 5,
height: 10,
getArea: function() {
return this.width * this.height;
}
};

console.log(rectangle.getArea()); // 50

✅ What Happens?
The getArea method calculates the area based on the object’s properties — commonly used in math operations , business logic , and data processing .

Example 3: Updating Object Properties

You can use methods to modify object properties.

let counter = {
value: 0,
increment: function() {
this.value++;
},
decrement: function() {
this.value--;
}
};

counter.increment();
counter.increment();
console.log(counter.value); // 2
counter.decrement();
console.log(counter.value); // 1

✅ What Happens?
The increment and decrement methods update the value property — showing how methods can manage internal state .

Summary

In this section, you’ve learned how to use object methods to add functionality to objects.

You explored:

  • How to greet users dynamically
  • How to calculate areas
  • How to update object properties

Object methods make your objects interactive , self-contained , and powerful .

Next, let’s explore the this keyword , which determines the context in which a function runs.

The This Keyword

The this keyword refers to the context in which a function is called. Its value depends on how the function is invoked and can vary between different scenarios.

Here’s a quick overview of how this behaves:

  • In global scope: Refers to the global object (window in browsers)
  • In object methods: Refers to the object itself
  • In constructor functions: Refers to the newly created instance
  • In arrow functions: Inherits this from the surrounding scope

Let’s see how this works with real examples.

Example 1: Using this in Object Methods

You can use this to access object properties within methods.

let user = {
name: "Bob",
greet: function() {
console.log(`Hello, ${this.name}!`);
}
};

user.greet(); // Hello, Bob!

✅ What Happens?
The this keyword refers to the user object, allowing access to its name property.

Example 2: Losing Context with this

If a method is called outside its object, this may lose its context.

let user = {
name: "Charlie",
greet: function() {
console.log(`Hello, ${this.name}!`);
}
};

let greetFunction = user.greet;
greetFunction(); // Hello, undefined!

✅ What Happens?
When greet is assigned to a variable and called globally, this no longer refers to the user object — highlighting why this can be tricky.

Example 3: Fixing Context with bind

You can use bind to explicitly set the context of this.

let user = {
name: "David",
greet: function() {
console.log(`Hello, ${this.name}!`);
}
};

let greetFunction = user.greet.bind(user);
greetFunction(); // Hello, David!

✅ What Happens?
The bind method ensures this always refers to the user object — a common fix for context issues .

Summary

In this section, you’ve learned how the this keyword determines the execution context of functions.

You explored:

  • How to use this in object methods
  • Why this can lose context
  • How to fix context issues with bind

Understanding this is crucial for writing predictable , reliable , and context-aware code .

Next, let’s explore how arrow functions behave differently with this compared to traditional functions.

Arrow Function di Object

Arrow functions have a unique behavior with this: they inherit it from their surrounding scope, rather than defining their own. This makes them behave differently in objects compared to regular functions.

Let’s see how arrow functions work with this in real examples.

Example 1: Arrow Functions Lose this

Arrow functions don’t bind their own this, so they can’t access object properties directly.

let user = {
name: "Eve",
greet: () => {
console.log(`Hello, ${this.name}!`);
}
};

user.greet(); // Hello, undefined!

✅ What Happens?
The arrow function doesn’t have its own this, so it inherits this from the global scope — resulting in undefined.

Example 2: Using Regular Functions Instead

Regular functions bind their own this, making them suitable for object methods.

let user = {
name: "Frank",
greet: function() {
console.log(`Hello, ${this.name}!`);
}
};

user.greet(); // Hello, Frank!

✅ What Happens?
The regular function binds this to the user object, allowing access to name.

Example 3: Combining Arrow Functions and Regular Functions

You can use arrow functions inside regular functions to maintain this.

let user = {
name: "Grace",
greet: function() {
setTimeout(() => {
console.log(`Hello, ${this.name}!`);
}, 1000);
}
};

user.greet(); // Hello, Grace! (after 1 second)

✅ What Happens?
The arrow function inside setTimeout inherits this from the greet method — avoiding context issues.

Summary

In this section, you’ve learned how arrow functions behave differently with this compared to regular functions.

You explored:

  • Why arrow functions lose this
  • When to use regular functions instead
  • How to combine both for better results

Arrow functions are great for lexical scoping , but they’re not always suitable for object methods.

Next, let’s explore getter and setter , which allow you to manage object properties dynamically.

Getter and Setter

Getters and setters are special methods that allow you to define custom behavior for accessing and modifying object properties. They provide a clean way to encapsulate logic around properties.

Get Riki Graha’s stories in your inbox

Join Medium for free to get updates from this writer.

Here’s the basic structure:

let object = {
get propertyName() {
// Code to retrieve the value
},
set propertyName(value) {
// Code to set the value
}
};

Let’s see how getters and setters work with real examples.

Example 1: Validating Input with Setters

You can use a setter to validate input before assigning it.

let user = {
_age: null,
set age(value) {
if (value >= 0 && value <= 120) {
this._age = value;
} else {
console.error("Invalid age.");
}
},
get age() {
return this._age;
}
};

user.age = 25;
console.log(user.age); // 25
user.age = -5; // Invalid age.

✅ What Happens?
The setter ensures only valid ages are assigned — widely used in form validation , data integrity , and business logic .

Example 2: Formatting Data with Getters

You can use a getter to format data before retrieving it.

let product = {
price: 100,
get formattedPrice() {
return `$${this.price.toFixed(2)}`;
}
};

console.log(product.formattedPrice); // $100.00

✅ What Happens?
The getter formats the price as a string — ideal for UI rendering , reports , and data presentation .

Example 3: Managing Private Properties

You can use getters and setters to simulate private properties.

let bankAccount = {
_balance: 0,
get balance() {
return this._balance;
},
set balance(value) {
if (value >= 0) {
this._balance = value;
} else {
console.error("Balance cannot be negative.");
}
}
};

bankAccount.balance = 500;
console.log(bankAccount.balance); // 500
bankAccount.balance = -100; // Balance cannot be negative.

✅ What Happens?
The _balance property is managed through getters and setters — enforcing data encapsulation and integrity .

Summary

In this section, you’ve learned how getters and setters allow you to manage object properties dynamically.

You explored:

  • How to validate input with setters
  • How to format data with getters
  • How to manage private properties

Getters and setters make your objects flexible , secure , and easy to maintain .

Next, let’s explore the problems with var , which highlight why let and const are better alternatives.

Pitfalls of Var

Before ES6, var was the only way to declare variables in JavaScript. However, it comes with several quirks and pitfalls that can lead to bugs and confusion.

Let’s explore the problems with var through real examples.

Example 1: Hoisting Issues

Variables declared with var are hoisted to the top of their scope, leading to unexpected behavior.

console.log(x); // undefined
var x = 5;
console.log(x); // 5

✅ What Happens?
The declaration of x is hoisted, but its assignment isn’t — causing undefined instead of an error.

Example 2: Function Scope vs Block Scope

var is function-scoped, not block-scoped, which can lead to unintended consequences.

if (true) {
var y = 10;
}
console.log(y); // 10

✅ What Happens?
The variable y is accessible outside the block — violating the principle of block scope.

Example 3: Redeclaration Issues

var allows redeclaration, which can overwrite existing variables accidentally.

var z = 20;
var z = 30;
console.log(z); // 30

✅ What Happens?
The second declaration overwrites the first — leading to potential bugs in larger codebases.

Summary

In this section, you’ve learned about the problems with var and why let and const are better alternatives.

You explored:

  • How hoisting causes unexpected behavior
  • Why var violates block scope
  • How redeclaration leads to bugs

Using let and const ensures safer , more predictable , and maintainable code .

Next, let’s explore destructuring , which simplifies access to complex data structures.

Destructuring

Destructuring allows you to extract values from arrays or objects into distinct variables. It’s a concise and readable way to unpack data.

Here’s the basic structure:

// Array destructuring
let [a, b] = array;

// Object destructuring
let { prop1, prop2 } = object;

Let’s see how destructuring works with real examples.

Example 1: Extracting Values from Arrays

You can destructure arrays to assign values to variables.

let numbers = [1, 2, 3];
let [first, second, third] = numbers;

console.log(first); // 1
console.log(second); // 2
console.log(third); // 3

✅ What Happens?
This assigns each element of the array to a variable — simplifying array manipulation .

Example 2: Extracting Values from Objects

You can destructure objects to access specific properties.

let user = { name: "Alice", age: 28, email: "alice@example.com" };
let { name, email } = user;

console.log(name); // Alice
console.log(email); // alice@example.com

✅ What Happens?
This extracts only the needed properties — reducing boilerplate and improving readability.

Example 3: Nested Destructuring

You can destructure nested objects and arrays.

let settings = {
theme: "dark",
notifications: {
enabled: true,
frequency: "daily"
}
};

let { notifications: { enabled, frequency } } = settings;

console.log(enabled); // true
console.log(frequency); // daily

✅ What Happens?
This extracts deeply nested properties — useful in API responses , configuration files , and complex data structures .

Summary

In this section, you’ve learned how destructuring simplifies access to complex data structures.

You explored:

  • How to destructure arrays
  • How to destructure objects
  • How to handle nested destructuring

Destructuring makes your code cleaner , shorter , and easier to read .

Next, let’s explore strict mode , which helps you write safer and more predictable code.

String Mode

Strict mode is a feature introduced in ES5 that enforces stricter parsing and error handling in your code. It helps catch common mistakes and “unsafe” actions, making your code more robust.

To enable strict mode, add "use strict"; at the top of your script or function.

Let’s see how strict mode works with real examples.

Example 1: Preventing Undeclared Variables

Strict mode prevents accidental creation of global variables.

"use strict";

x = 10; // ReferenceError: x is not defined

✅ What Happens?
Without strict mode, x would become a global variable. With strict mode, it throws an error — preventing accidental globals.

Example 2: Enforcing Proper this Binding

Strict mode ensures this doesn’t default to the global object.

"use strict";

function test() {
console.log(this); // undefined
}

test();

✅ What Happens?
In strict mode, this in non-method functions is undefined instead of the global object — avoiding confusing bugs.

Example 3: Disabling Reserved Keywords

Strict mode prevents the use of reserved keywords as variable names.

"use strict";

let let = 5; // SyntaxError: Unexpected strict mode reserved word

✅ What Happens?
This prevents misuse of reserved keywords — ensuring compatibility with future JavaScript versions.

Summary

In this section, you’ve learned how strict mode helps you write safer and more predictable code.

You explored:

  • How to prevent undeclared variables
  • How to enforce proper this binding
  • How to avoid reserved keywords

Using strict mode ensures cleaner , error-free , and future-proof code .

Finally, let’s explore the debugger tool, which helps you step through your code and identify issues.

Debugger

The debugger statement pauses your code execution and opens the browser’s developer tools, allowing you to inspect variables, step through code, and identify bugs.

Here’s the basic structure:

debugger;

Let’s see how the debugger works with real examples.

Example 1: Pausing Execution

You can use debugger to pause execution at a specific point.

function multiply(a, b) {
debugger;
return a * b;
}

console.log(multiply(5, 10));

✅ What Happens?
The debugger statement pauses execution inside the multiply function — letting you inspect a and b.

Example 2: Debugging Loops

You can use debugger to inspect loop iterations.

for (let i = 1; i <= 5; i++) {
debugger;
console.log(i);
}

✅ What Happens?
The debugger pauses execution during each iteration — helping you debug loop logic and variable changes .

Example 3: Debugging Asynchronous Code

You can use debugger to step through asynchronous workflows.

setTimeout(() => {
debugger;
console.log("Timeout executed!");
}, 1000);

✅ What Happens?
The debugger pauses execution inside the setTimeout callback — useful for debugging async operations .

Summary

In this section, you’ve learned how the debugger statement helps you identify and fix issues in your code.

You explored:

  • How to pause execution at specific points
  • How to debug loops
  • How to step through asynchronous code

Using the debugger ensures accurate , efficient , and bug-free development .

Final Thoughts

Congratulations! 🎉 You’ve completed the final chapter of our JavaScript Basics series !

In this chapter, you learned:

  • How closures encapsulate logic and manage state
  • How to add methods to objects for functionality
  • How the this keyword determines execution context
  • Why arrow functions behave differently with this
  • How getters and setters manage object properties dynamically
  • Why var is problematic and how let/const improve code quality
  • How destructuring simplifies access to complex data
  • How strict mode enforces safer coding practices
  • How the debugger statement helps identify and fix issues

These tools empower you to write clean , efficient , and maintainable JavaScript code — from simple scripts to complex applications .

As you continue your journey, remember: practice makes perfect . Apply what you’ve learned to real projects, experiment with new ideas, and never stop exploring.

You’ve done an amazing job reaching this point — keep going, stay curious, and enjoy the endless possibilities of programming!

Happy coding! 💻✨

--

--

Riki Graha
Riki Graha