Introduction to JavaScript
What is JavaScript?
JavaScript is a versatile, dynamic, and interpreted programming language that allows developers to create interactive and dynamic web content. It is one of the core web technologies, alongside HTML and CSS, and is essential for building interactive web applications.
Unlike HTML (which defines the page structure) and CSS (which styles the page), JavaScript adds behavior and interactivity. It allows developers to create animations, handle user input, update content in real-time, and much more.
Key Characteristics of JavaScript:
- Client-Side Execution: Runs directly in the user’s web browser.
- Dynamic Typing: Variables can hold any type of data and change types dynamically.
- Lightweight: It requires no compilation, and the browser can interpret it directly.
- Object-Oriented: Objects and prototypes form the foundation of JavaScript’s design.
- Cross-Platform: Works on all major browsers (Chrome, Firefox, Safari, etc.).
Example of JavaScript in Action
Here’s a simple example of JavaScript in an HTML document:
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<title>JavaScript Example</title>
</head>
<body>
<h1>Welcome to JavaScript</h1>
<button id=”clickMe”>Click Me</button>
<script>
document.getElementById(‘clickMe’).addEventListener(‘click’, function() {
alert(‘You clicked the button!’);
});
</script>
</body>
</html>
Explanation:
- The button element triggers an event listener using JavaScript.
- When you click the button, it runs a function that shows an alert box.
The Role of JavaScript in Web Development
JavaScript is at the heart of modern web development. It powers almost everything that makes web pages dynamic, interactive, and functional.
Key Roles of JavaScript in Web Development
- Adding Interactivity: Create animations, image sliders, modals, and event-driven responses to user interactions.
- Form Validation: Validates user input before it’s sent to the server.
- Single Page Applications (SPAs): Frameworks like React and Vue create seamless single-page applications like Gmail or Trello.
- Server-Side Scripting: With Node.js, JavaScript is used to build REST APIs and handle server-side logic.
- Dynamic Content Updates: Allows the page to be updated in real-time using AJAX and Fetch API without page reloads.
Example of Dynamic Content Update:
<button id=”changeContent”>Change Content</button>
<p id=”message”>This is the original message.</p>
<script>
document.getElementById(‘changeContent’).addEventListener(‘click’, function() {
document.getElementById(‘message’).textContent = ‘The content has been updated!’;
});
</script>
Explanation:
- Clicking the button updates the text inside the <p> tag.
- This is done dynamically without reloading the page.
Key Features and Benefits of JavaScript
JavaScript has unique features that make it one of the most widely-used programming languages in the world.
1. Cross-Browser Compatibility
JavaScript works on all major browsers (Chrome, Firefox, Safari, Edge, etc.) without any special requirements.
2. Asynchronous Capabilities
JavaScript can handle asynchronous tasks using callbacks, promises, and async/await, enabling smooth performance for long-running tasks like API requests.
3. Event-Driven Programming
Event listeners detect user actions like clicks, hovers, and key presses, and respond accordingly.
4. Platform Independence
JavaScript runs on all devices — desktops, laptops, smartphones, tablets, and even IoT devices — thanks to Node.js.
5. Full-Stack Development
With Node.js, you can write both frontend and backend logic in the same language.
JavaScript Versions and Updates
JavaScript has evolved over the years, with major improvements introduced in ECMAScript (ES) updates.
Timeline of Key ECMAScript Versions
- ES3 (1999): The first widely adopted version.
- ES5 (2009): Added strict mode, array methods like forEach, map, filter, and reduce.
- ES6 (2015): Introduced modern features like let, const, arrow functions, classes, template literals, and modules.
- ES7 – ES2023: Added features like async/await, optional chaining (?.), nullish coalescing (??), and logical assignment operators (&&=, ||=, ??=).
History and Evolution of JavaScript
JavaScript’s journey from a quick scripting tool to a modern, essential language is remarkable.
Timeline of Key Events
- 1995: Brendan Eich created JavaScript in 10 days for Netscape Navigator.
- 1996: Microsoft released JScript, a rival to Netscape’s JavaScript.
- 1997: JavaScript was standardized as ECMAScript 1.
- 2009: ES5 introduced key improvements like strict mode, JSON support, and new array methods.
- 2015: ES6 (ECMAScript 2015) introduced modern features like classes, arrow functions, template literals, and modules.
- 2018-2023: Incremental releases introduced async/await, optional chaining, and logical operators.
Why JavaScript is Essential for Modern Web Development
JavaScript has become essential for modern web development for several key reasons.
1. Interactive Web Pages
Without JavaScript, websites would be static and unresponsive. JavaScript enables click events, animations, form validation, and dynamic updates.
2. Full-Stack Development
With Node.js, you can use JavaScript on both the client side and server side, allowing developers to work on the full technology stack.
3. Vast Ecosystem of Libraries and Frameworks
Libraries like jQuery, and frameworks like React, Vue, and Angular make it faster and easier to build modern web applications.
4. Mobile and Desktop Apps
With frameworks like React Native, you can write mobile apps in JavaScript. Tools like Electron allow you to create desktop apps.
5. High Job Demand
JavaScript is one of the most sought-after skills in the tech industry. Knowledge of JavaScript, React, and Node.js opens up job opportunities in frontend, backend, and full-stack development.
Writing Your First JavaScript Program
Let’s get hands-on with our first JavaScript program. You can run this in your browser console or directly inside an HTML file.
Hello World Example
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<title>My First JavaScript Program</title>
</head>
<body>
<h1>Hello, World!</h1>
<script>
console.log(‘Hello, World!’);
alert(‘Welcome to JavaScript!’);
</script>
</body>
</html>
Explanation:
- console.log(‘Hello, World!’): Logs the message to the browser’s developer console.
- alert(‘Welcome to JavaScript!’): Shows a pop-up message when the page is loaded.
History and Evolution of JavaScript
The Birth of JavaScript
- Year: 1995
- Creator: Brendan Eich, a developer at Netscape.
- Timeframe: Brendan Eich created JavaScript in just 10 days.
- Purpose: To make web pages more interactive, dynamic, and user-friendly.
- Initial Name: JavaScript was initially called Mocha, then LiveScript, before being renamed JavaScript as part of a marketing strategy to ride on the popularity of Java.
The Rise of ECMAScript
To standardize the language, ECMAScript (ES) was introduced by ECMA International. Each new version of ECMAScript (ES) introduces modern features to the language.
Timeline of ECMAScript Versions
- 1997 – ECMAScript 1 (ES1): The first official version of JavaScript, mainly a standardization of the existing language.
- 1999 – ECMAScript 3 (ES3): Added support for regular expressions, try/catch error handling, and more robust string handling.
- 2009 – ECMAScript 5 (ES5): Introduced major features like:
- Strict Mode: Helps developers write better, error-free code.
- Array Methods: New array functions like forEach, map, filter, reduce, and every.
- JSON Support: Native support for JSON.parse() and JSON.stringify().
- 2015 – ECMAScript 6 (ES6): The most influential update, introducing:
- let and const for block-scoped variables.
- Arrow Functions for shorter function syntax.
- Classes for object-oriented programming.
- Modules (import/export) to break code into multiple files.
- Promises for better asynchronous programming.
- 2017-2023 – ECMAScript 2017+: Ongoing releases have added:
- Async/Await: Cleaner, more readable asynchronous code.
- Optional Chaining (?.): Safer access to deeply nested properties.
- Nullish Coalescing (??): Provides a way to assign default values.
- Logical Assignment Operators (&&=, ||=, ??=): Simplify conditional logic.
Why ECMAScript Matters
Every modern browser implements ECMAScript, and developers need to understand its changes. ES6 introduced many “must-know” features that modern development depends on.
How JavaScript Evolved from Browser to Full-Stack
- 1995: Used solely for client-side interactions in browsers.
- 2009: Node.js brought JavaScript to the server-side, enabling it to power backend logic.
- 2010-2023: The rise of JavaScript frameworks like React, Angular, and Vue revolutionized front-end development.
- Today: JavaScript is a full-stack language used on the client (browser) and the server (via Node.js).
Setting Up the Environment
Installing Node.js and npm
Node.js allows you to run JavaScript outside the browser, while npm (Node Package Manager) is used to manage project dependencies.
How to Install Node.js
- Visit: nodejs.org and download the latest LTS (Long-Term Support) version.
- Run the Installer: Follow the on-screen instructions for installation.
Verify Installation:
node -v
npm -v
Once installed, you will be able to run Node.js directly from the terminal and use npm to manage dependencies for your JavaScript projects.
Setting Up Visual Studio Code (VS Code)
VS Code is a lightweight, feature-rich code editor that works seamlessly with JavaScript and Node.js.
How to Install and Configure VS Code
- Download: Go to code.visualstudio.com and download the installer.
- Install Extensions:
- ESLint: Lints your code for syntax errors and best practices.
- Prettier: Automatically formats your code for readability.
- Live Server: Provides live reloading of static HTML, CSS, and JavaScript files.
Configuring Essential Extensions
To improve your development experience, configure the following extensions in VS Code:
- ESLint:
- Detects and highlights errors as you type.
- Ensures clean, maintainable, and bug-free code.
- Prettier:
- Automatically formats code to be clean and consistent.
- Live Server:
- Provides live reloading whenever changes are saved.
Using Git for Version Control
Git is essential for version control, collaboration, and managing code history.
How to Install Git
- Download from git-scm.com.
Verify Installation:
git –version
Basic Git Commands
Initialize a Repository:
git init
Add All Files:
git add .
Commit Changes:
git commit -m “Initial commit”
Push Changes to Remote Repository:
git push origin main
JavaScript Fundamentals
Variables and Data Types
JavaScript variables are used to store data. Modern JavaScript provides three ways to declare variables:
- let (block-scoped)
- const (block-scoped, immutable)
- var (function-scoped, outdated)
Example:
let age = 25;
const PI = 3.14;
var globalVariable = ‘I am global’;
Data Types
- Primitive Types:
- String: ‘Hello’
- Number: 42
- Boolean: true, false
- Undefined: A variable that has been declared but not assigned.
- Null: An explicitly empty value.
- Symbol: A unique and immutable primitive.
- BigInt: For integers larger than the limit of Number.
- Reference Types:
- Objects
- Arrays
- Functions
Operators and Expressions
JavaScript has a variety of operators to perform computations and logical comparisons.
Arithmetic Operators
- Addition (+), Subtraction (-), Multiplication (*), Division (/), Modulus (%).
Example:
let total = 10 + 5; // 15
let product = 10 * 2; // 20
Logical Operators
- AND (&&), OR (||), NOT (!).
Example:
let isAdult = true;
let hasLicense = false;
let canDrive = isAdult && hasLicense; // false
Control Flow
Control flow structures control the logic and execution of your program.
Conditional Statements
- if/else
let age = 18;
if (age >= 18) {
console.log(‘You are an adult.’);
} else {
console.log(‘You are a minor.’);
}
- switch
let day = ‘Monday’;
switch (day) {
case ‘Monday’:
console.log(‘Start of the week’);
break;
case ‘Friday’:
console.log(‘End of the week’);
break;
default:
console.log(‘Mid-week’);
}
Functions: Declarations, Expressions, and Arrow Functions
JavaScript functions allow you to reuse logic. Functions can be declared in several ways:
- Function Declaration:
function greet(name) {
return `Hello, ${name}`;
}
- Function Expression:
const greet = function(name) {
return `Hello, ${name}`;
}
- Arrow Function:
const greet = (name) => `Hello, ${name}`;
Error Handling with try…catch
Errors are inevitable, but try…catch lets you handle them gracefully.
Example:
try {
const data = JSON.parse(‘Invalid JSON’);
} catch (error) {
console.error(‘Error parsing JSON:’, error);
} finally {
console.log(‘Execution complete’);
}
Execution Contexts and Scopes
1. What is an Execution Context?
An Execution Context is an environment where JavaScript code is parsed and executed. It defines what variables, objects, and functions are accessible during code execution.
When the JavaScript engine runs code, it creates an execution context to keep track of:
- What variables are accessible?
- Where is the function currently executing?
- What is the value of this?
Types of Execution Contexts
- Global Execution Context (GEC)
- The default context where your entire JavaScript file runs.
- Variables declared outside of functions are part of the global context.
- The value of this in the global context points to the global object (window in browsers, global in Node.js).
- Function Execution Context (FEC)
- Every time a function is called, a new execution context is created.
- Each function execution has its own set of variables, arguments, and the value of this.
- Eval Execution Context
- When you use the eval() function, it creates a new execution context.
- This is not recommended as it introduces security issues and performance degradation.
Example of Global and Function Execution Contexts
// Global Context
let globalVar = ‘I am global’;
function outerFunction() {
// Function Context (FEC for outerFunction)
let outerVar = ‘I am outer’;
function innerFunction() {
// Function Context (FEC for innerFunction)
let innerVar = ‘I am inner’;
console.log(globalVar); // ‘I am global’ (global context)
console.log(outerVar); // ‘I am outer’ (outer function context)
console.log(innerVar); // ‘I am inner’ (local context)
}
innerFunction();
}
outerFunction();
Explanation:
- The Global Context is created when the script starts running.
- When outerFunction is called, a new Function Execution Context (FEC) is created.
- When innerFunction is called, another FEC is created, and the scope chain ensures globalVar and outerVar are accessible.
2. Lexical Environment and Scope Chain
A Lexical Environment is a mechanism for variable lookup. It defines how and where variable names are resolved during code execution.
When a function is created, it “remembers” where it was defined, and this creates its lexical environment. This is what makes closures possible.
How the Scope Chain Works
- Scope Chain: If a variable is not found in the current context, JavaScript “walks up” to its parent context to look for it.
- If it doesn’t find the variable in the parent context, it continues to the next outer context, eventually reaching the global context.
Example of Lexical Environment and Scope Chain
let a = ‘Global’;
function outerFunction() {
let b = ‘Outer’;
function innerFunction() {
let c = ‘Inner’;
console.log(a); // Global (global context)
console.log(b); // Outer (outerFunction context)
console.log(c); // Inner (innerFunction context)
}
innerFunction();
}
outerFunction();
Explanation:
- innerFunction can access a, b, and c, even though a and b are defined outside its scope.
- This happens because lexical scoping allows a function to “remember” where it was defined, not where it is called.
3. Function Scope vs. Block Scope
JavaScript originally had only function scope with var, but with the introduction of let and const, it now supports block scope as well.
Function Scope (with var)
- Variables declared with var are scoped to the nearest function.
- If var is declared outside a function, it is attached to the global object.
Example of Function Scope
function exampleFunction() {
var x = 10;
if (true) {
var x = 20; // This reassigns the same x (since var is function-scoped)
}
console.log(x); // 20
}
exampleFunction();
Explanation: The var x inside the if block modifies the x in the function because var is function-scoped, not block-scoped.
Block Scope (with let and const)
- let and const are block-scoped, meaning they exist only inside {} curly braces.
Example of Block Scope
function exampleFunction() {
let x = 10;
if (true) {
let x = 20; // This is a new x, scoped only inside this block
console.log(x); // 20
}
console.log(x); // 10 (not affected)
}
exampleFunction();
4. Understanding this in Different Contexts
The value of this depends on how the function is called, not where it was defined.
Rules for this
- Global Context: this refers to the global object (window in browsers, global in Node.js).
- Object Method: If a function is called as a method of an object, this points to the object.
- Arrow Functions: They do not have their own this. They inherit this from the parent scope.
- Explicit Binding: Using call, apply, and bind, you can control the value of this.
Example of this in Object Methods
const person = {
name: ‘Alice’,
greet() {
console.log(`Hello, my name is ${this.name}`);
}
};
person.greet(); // ‘Hello, my name is Alice’
Explanation: The this keyword inside greet points to the person object, as it is the caller.
Example of Arrow Functions and this
const person = {
name: ‘Alice’,
greet: () => {
console.log(`Hello, my name is ${this.name}`);
}
};
person.greet(); // ‘Hello, my name is undefined’
Explanation:
- Arrow functions don’t have their own this.
- this points to the global object instead of the person object.
5. Closures and Their Uses
A closure is a function that “remembers” its outer variables even when the outer function has finished executing.
How Closures Work
- When you define a function inside another function, the inner function “remembers” the variables of the outer function.
- Even when the outer function has finished execution, the inner function retains access to its parent scope.
Example of a Closure
function outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log(`Outer: ${outerVariable}, Inner: ${innerVariable}`);
};
}
const closureExample = outerFunction(‘outside’);
closureExample(‘inside’);
// Output: Outer: outside, Inner: inside
Explanation:
- outerVariable is “remembered” by innerFunction, even after outerFunction has completed execution.
Use Cases for Closures
- Data Privacy / Private Variables
- Function Factories (functions that return other functions)
- Event Handlers (where handlers have access to the context)
- Currying (breaking down a multi-argument function into multiple single-argument functions)
Summary of Key Concepts
Concept | Description |
Execution Context | Environment for running code |
Lexical Environment | Describes variable lookup mechanism |
Scope Chain | Chain of variable lookups |
this | Dynamic reference to the calling object |
Closures | Functions that “remember” outer variables |
Objects and Prototypes
1. What are JavaScript Objects?
JavaScript objects are collections of key-value pairs. Unlike primitive data types (like numbers, strings, and booleans), objects can store multiple values in a single variable. Objects can also have methods — functions associated with the object.
How to Create Objects
There are multiple ways to create objects in JavaScript:
Object Literal (Most Common)
const person = {
name: ‘Alice’,
age: 25,
greet: function() {
console.log(`Hello, my name is ${this.name}`);
}
};
person.greet(); // Output: Hello, my name is Alice
Using the Object() Constructor
const person = new Object();
person.name = ‘Bob’;
person.age = 30;
person.greet = function() {
console.log(`Hello, my name is ${this.name}`);
};
person.greet(); // Output: Hello, my name is Bob
Using Object.create() (Prototypal Inheritance)
const prototypePerson = {
greet: function() {
console.log(`Hello, my name is ${this.name}`);
}
};
const person = Object.create(prototypePerson);
person.name = ‘Charlie’;
person.greet(); // Output: Hello, my name is Charlie
Using ES6 Classes (More modern approach)
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
const person = new Person(‘Diana’, 28);
person.greet(); // Output: Hello, my name is Diana
2. Object Properties and Methods
Object Properties
Properties are key-value pairs stored inside an object.
Accessing Properties
Dot Notation:
const car = { brand: ‘Toyota’, model: ‘Corolla’ };
console.log(car.brand); // Output: Toyota
Bracket Notation (useful when the property name contains spaces or special characters):
const car = { ‘car-brand’: ‘Toyota’, model: ‘Corolla’ };
console.log(car[‘car-brand’]); // Output: Toyota
Adding Properties Dynamically:
const car = {};
car.brand = ‘Honda’;
car.model = ‘Civic’;
console.log(car); // { brand: ‘Honda’, model: ‘Civic’ }
Object Methods
A method is a function that belongs to an object.
How to Add Methods
Using Function Expressions
const dog = {
name: ‘Buddy’,
bark: function() {
console.log(‘Woof! Woof!’);
}
};
dog.bark(); // Output: Woof! Woof!
ES6 Shorthand Syntax
const dog = {
name: ‘Buddy’,
bark() {
console.log(‘Woof! Woof!’);
}
};
dog.bark(); // Output: Woof! Woof!
3. The Prototype Chain and Prototypal Inheritance
What is the Prototype?
Every JavaScript object has a hidden, internal property called [[Prototype]]. It links the object to another object (its prototype), from which it can inherit properties and methods.
How Does Prototypal Inheritance Work?
- When you try to access a property on an object, JavaScript looks for it on the object itself.
- If the property is not found, it looks for it in the object’s prototype.
- This lookup continues up the prototype chain until the property is found or the end of the chain is reached (null).
Example of the Prototype Chain
const animal = {
eat() {
console.log(‘I am eating’);
}
};
const dog = Object.create(animal);
dog.bark = function() {
console.log(‘Woof! Woof!’);
};
dog.eat(); // Output: I am eating (inherited from animal)
dog.bark(); // Output: Woof! Woof! (method defined on dog)
Explanation:
- dog inherits from animal via Object.create(animal).
- The method eat() exists in animal, so JavaScript “walks up” the prototype chain to find it.
4. Using Object.create() for Prototypes
The Object.create() method allows you to create an object with a specific prototype.
Example
const animal = {
speak() {
console.log(‘I am an animal’);
}
};
const dog = Object.create(animal);
dog.bark = function() {
console.log(‘Woof! Woof!’);
};
dog.speak(); // Output: I am an animal
dog.bark(); // Output: Woof! Woof!
5. Property Descriptors and Object.defineProperty()
Every property on an object has associated property descriptors. These control the behavior of properties.
Property Descriptors
- writable: Can the value be changed? (default: true)
- enumerable: Can the property be listed in loops like for…in? (default: true)
- configurable: Can the property be modified or deleted? (default: true)
Using Object.defineProperty()
This method allows you to define non-enumerable or read-only properties.
Example
const person = {};
Object.defineProperty(person, ‘name’, {
value: ‘Alice’,
writable: false,
enumerable: true,
configurable: false
});
console.log(person.name); // Alice
person.name = ‘Bob’; // This line will not change the value because writable is false
console.log(person.name); // Alice
6. Prototypes in ES6 Classes
When you create a class in ES6, all methods are automatically placed on the prototype of the object.
Example
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
const dog = new Animal(‘Buddy’);
dog.speak(); // Buddy makes a sound
7. Checking Object Properties
You can check if an object has a specific property using the following methods.
in operator
const dog = { breed: ‘Labrador’ };
console.log(‘breed’ in dog); // true
hasOwnProperty()
const dog = { breed: ‘Labrador’ };
console.log(dog.hasOwnProperty(‘breed’)); // true
8. Object Methods
JavaScript provides several useful object utility methods.
Method | Description |
Object.keys(obj) | Returns an array of all property names |
Object.values(obj) | Returns an array of all property values |
Object.entries(obj) | Returns an array of key-value pairs |
Object.assign() | Copies properties from one object to another |
Object.freeze() | Makes an object immutable (read-only) |
Object.seal() | Prevents adding new properties but allows modification of existing ones |
Summary of Key Concepts
Concept | Description |
Object | A collection of key-value pairs |
Prototype | The object from which other objects inherit |
Property Descriptors | Define if a property is writable, enumerable, or configurable |
Object.create() | Creates a new object with a specific prototype |
ES6 Classes | Modern syntax for creating objects and prototypes |
Functions and Closures
1. What are Functions in JavaScript?
A function is a reusable block of code designed to perform a specific task. Functions make it easier to avoid repetition, structure logic, and keep your code clean and modular.
Key Characteristics of Functions
- Reusable: Functions can be called multiple times.
- Accept Inputs: Functions accept parameters.
- Return Outputs: Functions can return values.
- Modular: Functions help split complex logic into smaller, maintainable parts.
Types of Functions in JavaScript
Function Declaration (hoisted)
function greet(name) {
return `Hello, ${name}`;
}
console.log(greet(‘Alice’)); // Output: Hello, Alice
Function Expression (not hoisted)
const greet = function(name) {
return `Hello, ${name}`;
};
console.log(greet(‘Bob’)); // Output: Hello, Bob
Arrow Functions (ES6+)
const greet = (name) => `Hello, ${name}`;
console.log(greet(‘Charlie’)); // Output: Hello, Charlie
Key Differences:
- Function Declarations are hoisted, meaning they can be called before their definition.
- Function Expressions and Arrow Functions are not hoisted.
2. Function Parameters and Arguments
Default Parameters
If a parameter is not provided, JavaScript assigns the default value.
function greet(name = ‘Guest’) {
console.log(`Hello, ${name}`);
}
greet(); // Output: Hello, Guest
greet(‘David’); // Output: Hello, David
Rest Parameters
The …rest syntax allows functions to accept multiple arguments as an array.
function sum(…numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // Output: 10
Arguments Object (Non-Arrow Functions Only)
In traditional (non-arrow) functions, the arguments object contains all arguments passed to the function.
function showArguments() {
console.log(arguments);
}
showArguments(1, ‘hello’, true);
// Output: [1, “hello”, true]
3. Function Scope and Hoisting
Function Scope
Variables declared inside a function are not accessible outside that function.
function exampleFunction() {
let localVar = ‘I am local’;
console.log(localVar); // Accessible here
}
exampleFunction();
console.log(localVar); // ReferenceError: localVar is not defined
Function Hoisting
Function declarations are hoisted to the top of their scope, meaning you can call them before they are defined.
helloWorld(); // Works even before the function definition
function helloWorld() {
console.log(‘Hello, World!’);
}
Function expressions and arrow functions are not hoisted.
greet(); // ReferenceError: Cannot access ‘greet’ before initialization
const greet = () => {
console.log(‘Hi there!’);
};
4. Callbacks and Higher-Order Functions
What is a Callback Function?
A callback function is a function passed as an argument to another function and is called later.
function processUserInput(callback) {
const userInput = ‘Hello’;
callback(userInput);
}
processUserInput((input) => console.log(`User input is: ${input}`));
// Output: User input is: Hello
What is a Higher-Order Function?
A higher-order function is a function that can accept other functions as arguments or return another function.
Example: Higher-Order Function
function multiplyBy(factor) {
return function(number) {
return number * factor;
};
}
const double = multiplyBy(2);
console.log(double(5)); // Output: 10
5. Closures: A Deep Dive
A closure is created when a function “remembers” its outer scope. Even after the outer function has finished executing, the inner function still has access to the outer function’s variables.
How Closures Work
- When a function is returned from another function, it “remembers” the variables from its lexical environment.
- This allows the inner function to access variables that were in scope at the time the outer function was defined.
Example of a Closure
function outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log(`Outer: ${outerVariable}, Inner: ${innerVariable}`);
};
}
const closureExample = outerFunction(‘outside’);
closureExample(‘inside’);
// Output: Outer: outside, Inner: inside
Practical Use Cases of Closures
- Data Privacy / Private Variables
- Function Factories (functions that create other functions)
- Event Handlers (functions bound to UI events)
- Currying (transforming functions into multiple functions)
Example: Private Variables
function createCounter() {
let count = 0;
return function() {
count++;
console.log(`Current count: ${count}`);
};
}
const counter = createCounter();
counter(); // Output: Current count: 1
counter(); // Output: Current count: 2
6. Function Methods (call, apply, bind)
call()
Calls a function with a specific this value and arguments.
const person = {
name: ‘Alice’,
};
function greet(greeting) {
console.log(`${greeting}, ${this.name}`);
}
greet.call(person, ‘Hello’); // Output: Hello, Alice
apply()
Like call(), but it takes an array of arguments instead of individual arguments.
greet.apply(person, [‘Hi’]); // Output: Hi, Alice
bind()
Returns a new function with this bound to a specific value.
const boundGreet = greet.bind(person, ‘Hey’);
boundGreet(); // Output: Hey, Alice
7. Currying and Partial Application
What is Currying?
Currying is a technique of transforming a multi-argument function into a chain of functions, each accepting a single argument.
Example of Currying
function add(x) {
return function(y) {
return x + y;
};
}
const add5 = add(5);
console.log(add5(3)); // Output: 8
8. Arrow Functions: Key Differences
Feature | Traditional Functions | Arrow Functions |
this context | Dynamic (based on caller) | Lexically bound |
arguments object | Available | Not available |
Syntax | Verbose | Short and concise |
Summary of Key Concepts
Concept | Description |
Function | A reusable block of code |
Closure | A function that “remembers” outer variables |
Call, Apply, Bind | Control this in function calls |
Currying | Break down a function into smaller parts |
Rest Parameters | Accept multiple arguments as an array |
Practice Questions
- What is the main difference between var, let, and const in function declarations?
- How do you create a private variable using closures?
- What does bind() do, and how does it differ from call()?
Asynchronous JavaScript
1. What is Asynchronous JavaScript?
Asynchronous JavaScript allows for non-blocking code execution. This means the program does not have to “wait” for one task to finish before moving on to the next task. Instead, it can continue running other code while waiting for an operation (like a network request) to complete.
Why is Asynchronous Programming Important?
- Prevents Blocking: Without async logic, slow operations like API calls or file reads could block the entire program.
- Smooth User Experience: Allows web applications to remain interactive while data is being loaded.
- Essential for Web Development: Async logic powers APIs, timers, event listeners, and more.
2. The Event Loop and Callback Queue
The Event Loop is one of the most important concepts in JavaScript. It allows asynchronous code to be executed in a non-blocking manner.
How the Event Loop Works
- Call Stack: Keeps track of function calls and execution.
- Web APIs: External APIs like setTimeout, fetch, and DOM events are handled here.
- Callback Queue: Functions that are ready to be executed after asynchronous tasks finish.
- Event Loop: Continuously checks if the call stack is empty and moves pending callbacks from the queue to the stack.
Example of Event Loop
console.log(‘Start’);
setTimeout(() => {
console.log(‘Callback function executed’);
}, 2000);
console.log(‘End’);
Output:
Start
End
Callback function executed
Explanation:
- Start is printed.
- setTimeout is sent to the Web API (which waits 2 seconds) and then moves its callback to the Callback Queue.
- End is printed immediately because the main thread isn’t blocked.
- After 2 seconds, the Event Loop moves the callback from the queue to the call stack, and it runs.
3. Callbacks: Definition and Usage
A callback is a function passed as an argument to another function. It is executed after the completion of the task.
Why Use Callbacks?
- To handle asynchronous tasks (like API requests).
- To run functions after a certain task (like animations or timers) is complete.
Example of Callbacks
function downloadFile(fileName, callback) {
console.log(`Starting download for ${fileName}…`);
setTimeout(() => {
console.log(`${fileName} download complete.`);
callback();
}, 3000);
}
downloadFile(‘myFile.txt’, () => {
console.log(‘File is ready to use.’);
});
Output:
Starting download for myFile.txt…
(myFile.txt download completes after 3 seconds)
File is ready to use.
Explanation:
- The setTimeout() simulates a file download.
- The callback() runs after the file has been downloaded.
4. Promises: Chaining and Error Handling
A Promise represents a value that will be available in the future. A promise can be in one of three states:
- Pending: Initial state, not resolved or rejected.
- Fulfilled: Operation was successful.
- Rejected: Operation failed.
How to Create a Promise
const promise = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve(‘Operation was successful’);
} else {
reject(‘Operation failed’);
}
});
promise
.then(result => console.log(result)) // If resolved
.catch(error => console.log(error)); // If rejected
Chaining Promises
You can chain multiple .then() calls to process multiple asynchronous tasks.
const getUserData = () => {
return new Promise((resolve) => {
setTimeout(() => resolve(‘User Data’), 1000);
});
};
const getPosts = () => {
return new Promise((resolve) => {
setTimeout(() => resolve(‘User Posts’), 1000);
});
};
getUserData()
.then(userData => {
console.log(userData); // User Data
return getPosts();
})
.then(posts => console.log(posts)); // User Posts
5. Async/Await: Writing Cleaner Asynchronous Code
The async/await syntax is a cleaner way to handle promises. It allows you to write asynchronous code like synchronous code, which is easier to read and debug.
How Async/Await Works
- async: Declares a function as asynchronous.
- await: Pauses execution until the Promise is resolved.
Example of Async/Await
async function fetchData() {
try {
const userResponse = await fetch(‘https://jsonplaceholder.typicode.com/users/1’);
const user = await userResponse.json();
console.log(‘User Data:’, user);
} catch (error) {
console.error(‘Error fetching user data:’, error);
}
}
fetchData();
Explanation:
- await pauses execution until the fetch call completes.
- If an error occurs, the try…catch block handles it.
6. Common Asynchronous Patterns
1. Parallel Execution
Use Promise.all() to run multiple asynchronous tasks simultaneously.
const task1 = new Promise((resolve) => setTimeout(() => resolve(‘Task 1 complete’), 2000));
const task2 = new Promise((resolve) => setTimeout(() => resolve(‘Task 2 complete’), 1000));
Promise.all([task1, task2]).then(results => {
console.log(results); // [‘Task 1 complete’, ‘Task 2 complete’]
});
2. Race Conditions
Promise.race() returns the result of the first promise to resolve.
const slowTask = new Promise(resolve => setTimeout(() => resolve(‘Slow Task’), 3000));
const fastTask = new Promise(resolve => setTimeout(() => resolve(‘Fast Task’), 1000));
Promise.race([slowTask, fastTask]).then(result => console.log(result));
// Output: Fast Task
3. Sequential Execution
Run multiple asynchronous tasks one after another.
async function runTasksSequentially() {
const result1 = await new Promise(resolve => setTimeout(() => resolve(‘Task 1 complete’), 2000));
console.log(result1);
const result2 = await new Promise(resolve => setTimeout(() => resolve(‘Task 2 complete’), 1000));
console.log(result2);
}
runTasksSequentially();
7. Error Handling in Async Code
Error handling is essential for asynchronous code. Without it, unhandled rejections can crash your application.
Error Handling in Promises
Use .catch() to catch errors in promises.
const promise = new Promise((resolve, reject) => reject(‘Error occurred!’));
promise.catch(error => console.log(error)); // Output: Error occurred!
Error Handling in Async/Await
try…catch is used to handle errors in async/await functions.
async function fetchData() {
try {
const response = await fetch(‘invalid-url’);
const data = await response.json();
} catch (error) {
console.error(‘Error:’, error);
}
}
fetchData();
Summary of Key Concepts
Concept | Description |
Event Loop | Handles asynchronous tasks like timers |
Callback | Function executed after an async task |
Promise | Represents a future value |
Async/Await | Write async code like synchronous code |
Error Handling | Manage errors in async code using catch |
Practice Questions
- What is the event loop, and how does it affect asynchronous tasks?
- Write an example of a function using async/await.
- What is the difference between Promise.all() and Promise.race()?
- What is the benefit of using try…catch in async/await?
JavaScript Weird Parts and Quirks
1. Hoisting of Variables and Functions
Hoisting is a mechanism in JavaScript where variable and function declarations are moved to the top of their containing scope (global or function scope) during the compilation phase, before code execution.
How Hoisting Works
- Variables declared with var are hoisted but not initialized.
- let and const are also hoisted but remain in a Temporal Dead Zone (TDZ) until the line where they are declared.
- Function Declarations are fully hoisted, which allows them to be called before their definition.
Examples of Hoisting
1. Hoisting with var
console.log(x); // Output: undefined (hoisted but not initialized)
var x = 10;
console.log(x); // Output: 10
Explanation:
- The var x is hoisted to the top, but only the declaration is hoisted, not its initialization.
- So, the first console.log(x) outputs undefined.
2. Hoisting with let and const
console.log(y); // ReferenceError: Cannot access ‘y’ before initialization
let y = 20;
Explanation:
- let and const are hoisted but exist in the Temporal Dead Zone.
- Accessing y before the declaration throws a ReferenceError.
3. Hoisting of Functions
greet(); // Output: Hello!
function greet() {
console.log(‘Hello!’);
}
Explanation:
- Since function declarations are fully hoisted, the function can be called before its definition.
2. Type Coercion and Loose Equality (== vs ===)
Type coercion is the automatic conversion of values from one data type to another.
- == (loose equality): Converts values to the same type before comparing.
- === (strict equality): No type conversion; values and types must both be the same.
Examples of Type Coercion
1. Loose Equality (==)
console.log(5 == ‘5’); // true (because ‘5’ is converted to a number)
console.log(false == 0); // true (false is coerced to 0)
console.log(” == 0); // true (empty string is coerced to 0)
2. Strict Equality (===)
console.log(5 === ‘5’); // false (no type conversion)
console.log(false === 0); // false (different types)
console.log(” === 0); // false (empty string and 0 are not the same)
3. NaN and Its Unexpected Behavior
NaN (Not-a-Number) is a special numeric value that represents invalid mathematical operations.
Why is NaN Weird?
- NaN is not equal to NaN.
- typeof NaN returns “number”.
Examples of NaN Quirks
console.log(NaN === NaN); // false
console.log(typeof NaN); // ‘number’
console.log(Number(‘abc’)); // NaN
How to Check for NaN
Use Number.isNaN() to check for NaN (better than isNaN()).
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN(‘abc’)); // false
4. Falsy and Truthy Values in JavaScript
Truthy and falsy values affect conditionals (if, while, etc.).
Falsy Values
- false
- 0
- ” (empty string)
- null
- undefined
- NaN
Truthy Values
- All values except falsy values.
Example of Falsy Values
if (0) {
console.log(‘This will not run’);
}
if (”) {
console.log(‘This will not run’);
}
Example of Truthy Values
if ([]) {
console.log(‘This will run’);
}
if (‘hello’) {
console.log(‘This will run’);
}
5. Floating-Point Arithmetic Errors
JavaScript uses binary floating-point arithmetic (IEEE 754), which causes issues with precision.
Examples of Floating-Point Issues
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.7 === 0.8); // true
Solution
Use toFixed() or multiplication workaround.
console.log((0.1 * 10 + 0.2 * 10) / 10); // 0.3
6. The Arguments Object and Its Quirks
The arguments object is an array-like object that holds all arguments passed to a function.
Example of Arguments Object
function sum() {
console.log(arguments);
}
sum(1, 2, 3); // [1, 2, 3]
Quirks of Arguments
- Not available in arrow functions.
- Not a real array — no .map() or .filter().
- Use Array.from(arguments) to convert it to a real array.
7. How Arrays Behave with delete and Empty Slots
What Happens When You Use delete on Arrays?
const fruits = [‘Apple’, ‘Banana’, ‘Cherry’];
delete fruits[1];
console.log(fruits); // [‘Apple’, <empty slot>, ‘Cherry’]
- delete removes the element but leaves an empty slot.
- Instead, use splice().
Solution
const fruits = [‘Apple’, ‘Banana’, ‘Cherry’];
fruits.splice(1, 1);
console.log(fruits); // [‘Apple’, ‘Cherry’]
8. Differences Between var, let, and const
var | let | const |
Function Scope | Block Scope | Block Scope |
Can be redeclared | Cannot be redeclared | Cannot be redeclared |
Can be updated | Can be updated | Cannot be updated |
Hoisted | Hoisted (TDZ) | Hoisted (TDZ) |
Examples of var, let, and const
var x = 10;
let y = 20;
const z = 30;
9. The Misleading typeof null
Why Does typeof null Return “object”?
This is a bug in JavaScript. Originally, JavaScript stored internal data types as type tags, and null was mistakenly assigned as object.
Example
console.log(typeof null); // ‘object’
How to Check for null Correctly
if (value === null) {
console.log(‘Value is null’);
}
10. Automatic Semicolon Insertion (ASI)
JavaScript automatically inserts semicolons at the end of statements, but this can lead to subtle bugs.
Problematic Case
return
{
name: ‘Alice’
}
Output: undefined (because JavaScript inserts a semicolon after return)
Solution
return {
name: ‘Alice’
};
Summary of Key Concepts
Concept | Description |
Hoisting | Variables/functions moved to the top |
Type Coercion | Automatic conversion of data types |
NaN | Not-a-Number, but of type “number” |
Falsy Values | Values that convert to false in Boolean |
Floating-Point Errors | Inaccurate decimal calculations |
delete in Arrays | Leaves empty slots in arrays |
typeof null | Returns “object” due to legacy issues |
JavaScript Weird Parts and Quirks
1. Global Object Quirk (this in Global Context)
What is the Global Object?
The global object refers to the “top-level” object in a JavaScript environment. Its name differs depending on the context:
- In browsers, the global object is window.
- In Node.js, the global object is global.
Weird Behavior of this in the Global Context
console.log(this); // In browser, it logs the ‘window’ object.
console.log(this === window); // true (only in browser)
Quirky Behavior
- In the global scope, this points to the global object.
- In strict mode, this is undefined in the global scope.
Example in Strict Mode
‘use strict’;
console.log(this); // undefined
This prevents accidental use of the global object, a common source of bugs.
2. with Statement (Why You Should Avoid It)
The with statement extends the scope chain for a specific object, making it a bad practice.
How it Works
const person = { name: ‘Alice’, age: 25 };
with (person) {
console.log(name); // Alice
console.log(age); // 25
}
Why It’s a Quirk
- Ambiguity: The variable name could be from the person object or from the global scope.
- Performance Issues: It changes the scope chain, slowing down execution.
Best Practice
Avoid using with as it is discouraged and might be removed in the future.
3. Implicit Type Conversion (More Weird Coercion Cases)
Implicit type coercion happens when JavaScript converts one type to another during operations.
Strange Coercion Examples
console.log(true + false); // 1 (true = 1, false = 0)
console.log(‘5’ – 3); // 2 (‘5’ becomes 5)
console.log(‘5′ + 3); // ’53’ (concatenation, not arithmetic)
console.log([1, 2] + [3, 4]); // ‘1,23,4’ (arrays become strings)
Coercion with Objects
const obj = { toString() { return ‘Hello’; } };
console.log(obj + ‘ World’); // ‘Hello World’
Explanation: The object is converted to a string using its toString() method.
4. Strange Behavior of Empty Arrays and Empty Objects
Array Quirks
console.log([] + []); // ” (two empty arrays are converted to empty strings)
console.log([] + {}); // ‘[object Object]’ (empty array becomes empty string, {} becomes “[object Object]”)
console.log({} + []); // 0 (parsed as an empty code block, and the addition of [] as a number gives 0)
Object Quirks
console.log({} + {}); // [object Object][object Object]
Explanation:
- If you add two objects with +, it coerces them to strings using Object.prototype.toString(), which returns “[object Object]”.
5. Weird Loops (for…in vs. for…of)
for…in (Iterates Over Keys)
const array = [‘a’, ‘b’, ‘c’];
for (const key in array) {
console.log(key); // Logs 0, 1, 2 (the indices)
}
Explanation:
- for…in iterates over keys (property names), even on arrays.
for…of (Iterates Over Values)
for (const value of array) {
console.log(value); // Logs ‘a’, ‘b’, ‘c’
}
Explanation:
- for…of iterates over values in iterable objects (like arrays, strings, etc.).
6. The Quirk of Floating-Point Modulus
Due to floating-point precision errors, modulus operations don’t always return expected results.
Example
console.log(0.3 % 0.1); // 0.09999999999999995 (not 0)
Explanation:
- JavaScript’s floating-point system is imprecise.
- To avoid this, use integer arithmetic.
7. Function Length Property
The length property of a function tells you how many parameters the function expects.
Example
function foo(a, b, c) {}
console.log(foo.length); // 3 (number of expected parameters)
8. The Quirk of Adding Properties to Primitives
Example
const str = ‘Hello’;
str.language = ‘English’;
console.log(str.language); // undefined
Explanation:
- Primitive values (like strings) are not objects.
- Temporary wrapper objects are created for methods like .length and .toUpperCase(), but they disappear immediately.
9. The Quirk of isNaN()
Problem with isNaN()
console.log(isNaN(‘abc’)); // true (because ‘abc’ is coerced to NaN)
console.log(isNaN(NaN)); // true
Explanation:
- isNaN() converts the argument to a number and checks if it’s NaN.
- Instead, use Number.isNaN() for a strict check.
Solution
console.log(Number.isNaN(‘abc’)); // false
console.log(Number.isNaN(NaN)); // true
10. Quirky Array Sorting
Problem with Default Array Sort
const nums = [1, 10, 2, 21];
nums.sort();
console.log(nums); // [1, 10, 2, 21] (sorted as strings)
Solution
nums.sort((a, b) => a – b); // [1, 2, 10, 21]
Explanation:
- The default sort method converts items to strings and compares character codes.
11. The Quirk of Empty Return Statements
Problem
function doSomething() {
return
{ message: ‘Hello’ };
}
console.log(doSomething()); // undefined
Explanation:
- A semicolon is automatically inserted after return, causing the object to be unreachable.
Solution
function doSomething() {
return {
message: ‘Hello’
};
}
12. Quirk of Infinity
Quirks
- Positive Infinity and Negative Infinity exist.
- Division by zero gives Infinity.
- You can check if a number is finite using isFinite().
Example
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
13. Weird Logical Operators
Logical operators have some unexpected behavior.
Examples
console.log(true || ‘Hello’); // true
console.log(false || ‘Hello’); // ‘Hello’
console.log(false && ‘Hello’); // false
console.log(true && ‘Hello’); // ‘Hello’
Summary of Key Concepts
Concept | Description |
Global Object | this points to the global object |
Implicit Coercion | Auto-conversion of types |
Floating-Point Errors | Imprecision in arithmetic |
Weird Array Sort | Array.sort() sorts by strings |
Logical Operators | Unexpected short-circuit behavior |
Hoisting | Variables/functions move to the top |
Falsy Values | Values that convert to false in Boolean |
Automatic Semicolon | Can cause unintended return issues |
JavaScript Weird Parts and Quirks (Ultimate Guide)
1. Hoisting of Variables and Functions (Revisited)
Hoisting quirks often confuse developers because of how declarations are lifted to the top of their scope.
Unusual Hoisting Behavior with var
function hoistQuirk() {
console.log(a); // undefined
var a = 10;
}
hoistQuirk();
Explanation:
- The declaration var a is hoisted to the top, but its initialization (a = 10) is not.
- Hence, console.log(a) returns undefined.
Unusual Hoisting with Functions
function example() {
console.log(foo()); // Works because foo is hoisted
function foo() {
return ‘Hello, World!’;
}
}
example();
Explanation:
- Function declarations are fully hoisted, allowing them to be called before they are defined.
2. Type Coercion Oddities
Type coercion leads to some very unusual and counter-intuitive results.
Bizarre Coercion Examples
console.log([] + []); // ” (empty arrays are converted to empty strings)
console.log({} + []); // ‘[object Object]’ ({} is converted to string)
console.log([] + {}); // ‘[object Object]’ (empty array becomes ”, {} is converted to string)
console.log(true + true); // 2 (true is converted to 1)
console.log(‘5’ – 1); // 4 (‘5’ is converted to 5)
Solutions to Avoid Coercion
- Use strict equality (===).
- Explicitly convert data types using Number(), String(), and Boolean().
3. NaN (Not-a-Number) Paradoxes
Weird NaN Examples
console.log(NaN === NaN); // false (NaN is not equal to itself)
console.log(typeof NaN); // ‘number’ (even though it’s “Not-a-Number”)
console.log(0 / 0); // NaN
How to Handle NaN
- Use Number.isNaN() for strict checks.
- Avoid coercion-based checks like isNaN().
4. Falsy and Truthy Values (Surprising Ones)
Surprising Falsy Values
- false
- 0 (zero)
- ” (empty string)
- null
- undefined
- NaN
Surprising Truthy Values
- [] (empty array)
- {} (empty object)
- Functions (all functions are truthy)
- Non-empty strings (‘0’, ‘false’, etc.)
Quirky Falsy Value Example
if (‘false’) {
console.log(‘This will run’); // True (because ‘false’ is a non-empty string)
}
5. Floating-Point Precision Issues (Revisited)
More Examples of Floating-Point Errors
console.log(0.1 + 0.2); // 0.30000000000000004 (not 0.3)
console.log(0.3 – 0.1); // 0.19999999999999998 (not 0.2)
console.log(1.005.toFixed(2)); // ‘1.00’ (not ‘1.01’)
Solution
const preciseSum = (a, b) => Math.round((a + b) * 100) / 100;
console.log(preciseSum(0.1, 0.2)); // 0.3
6. Strange Behavior of delete on Arrays
Weird delete Examples
const arr = [1, 2, 3];
delete arr[1];
console.log(arr); // [1, <empty>, 3] (not [1, 3])
console.log(arr.length); // 3 (length does not change)
Solution
Use splice() instead of delete.
const arr = [1, 2, 3];
arr.splice(1, 1);
console.log(arr); // [1, 3]
7. The Misleading typeof null
Why typeof null Returns “object”
console.log(typeof null); // ‘object’
Explanation:
- This is a legacy bug in JavaScript.
- null was supposed to be a unique “null” type, but it mistakenly got labeled as an “object”.
Solution
if (value === null) {
console.log(‘Value is null’);
}
8. Quirky Array Sorting
Strange Sorting Behavior
const numbers = [1, 10, 2, 21];
numbers.sort();
console.log(numbers); // [1, 10, 2, 21] (not sorted as expected)
Solution
numbers.sort((a, b) => a – b);
console.log(numbers); // [1, 2, 10, 21]
9. Automatic Semicolon Insertion (ASI) Issues
Unexpected Behavior
function doSomething() {
return
{
message: ‘Hello’
};
}
console.log(doSomething()); // undefined (because ASI inserts a semicolon after return)
Solution
function doSomething() {
return {
message: ‘Hello’
};
}
10. Function Quirks (Default Parameters and Rest/Spread)
Default Parameter Quirks
function greet(name = ‘Guest’) {
console.log(`Hello, ${name}`);
}
greet(); // Hello, Guest
greet(undefined); // Hello, Guest (because undefined triggers the default)
greet(null); // Hello, null (null is not undefined)
Rest Parameters
function sum(…nums) {
return nums.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
11. Quirk of Infinity
Examples of Infinity
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
Explanation:
- Division by zero returns Infinity.
- Infinity behaves like a number in many contexts.
Check for Infinity
console.log(isFinite(1 / 0)); // false
console.log(isFinite(10 / 2)); // true
12. Quirky Object Property Keys
Property Keys as Strings
const obj = { 1: ‘one’, true: ‘boolean’, null: ‘null’ };
console.log(obj[‘1’]); // ‘one’
console.log(obj[‘true’]); // ‘boolean’
console.log(obj[‘null’]); // ‘null’
Why This Happens
- Object property keys are always strings.
- If you use a number, it’s converted to a string (‘1’).
13. The Quirk of this in Arrow Functions
Quirky Example
const person = {
name: ‘Alice’,
greet: () => {
console.log(`Hello, my name is ${this.name}`);
}
};
person.greet(); // Hello, my name is undefined
Explanation:
- Arrow functions do not have their own this.
- Arrow functions inherit this from the lexical environment.
Summary of Key Concepts
Concept | Description |
Type Coercion | Odd conversions (‘5′ + 1 = ’51’) |
NaN | Not-a-Number is of type “number” |
delete on Arrays | Leaves “holes” in arrays |
typeof null | Returns “object” due to legacy issues |
ASI | Automatic semicolon insertion |
Infinity | Division by zero returns Infinity |





