Basic of JavaScript — Chapter 7 [End]
Learn HTML, CSS, and JavaScript Programming from Beginner to Expert
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
thisKeyword : Understanding howthisworks in different contexts - Arrow Functions in Objects : Why arrow functions behave differently with
this - Getter and Setter : Managing object properties dynamically
- Problems with
var: Whyletandconstare 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 (
windowin browsers) - In object methods: Refers to the object itself
- In constructor functions: Refers to the newly created instance
- In arrow functions: Inherits
thisfrom 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
thisin object methods - Why
thiscan 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
varviolates 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
thisbinding - 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
thiskeyword determines execution context - Why arrow functions behave differently with
this - How getters and setters manage object properties dynamically
- Why
varis problematic and howlet/constimprove code quality - How destructuring simplifies access to complex data
- How strict mode enforces safer coding practices
- How the
debuggerstatement 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! 💻✨









