The this keyword in JavaScript refers to the object that is currently executing the function. Its value is determined by how a function is called.
Key points:
- The value of
thiscan change depending on the context - In the global scope,
thisrefers to the global object (window in browsers, global in Node.js) - In a method,
thisrefers to the object the method was called on - In a constructor function,
thisrefers to the newly created instance - The value of
thiscan be explicitly set usingcall(),apply(), orbind()
Examples:
- Global context:
console.log(this === window); // true (in a browser)- Object method:
const obj = {
name: "John",
greet: function() {
console.log(`Hello, ${this.name}!`);
}
};
obj.greet(); // Output: Hello, John!- Constructor function:
function Person(name) {
this.name = name;
}
const john = new Person("John");
console.log(john.name); // Output: John- Explicit binding:
function greet() {
console.log(`Hello, ${this.name}!`);
}
const person = { name: "Alice" };
greet.call(person); // Output: Hello, Alice!- Arrow functions:
const obj = {
name: "John",
greet: () => {
console.log(`Hello, ${this.name}!`);
}
};
obj.greet(); // Output: Hello, undefined!
// Arrow functions do not have their own `this` bindingDiagram: this in Different Contexts
┌─────────────────────┐
│ Global Scope │
│ this → global obj │
│ ┌─────────────────┐│
│ │ Function ││
│ │ this → dynamic ││
│ │ ┌─────────────┐││
│ │ │Object Method│││
│ │ │ this → obj │││
│ │ └─────────────┘││
│ └─────────────────┘│
└─────────────────────┘
Prototypal inheritance is a fundamental concept in JavaScript where objects can inherit properties and methods from other objects through a prototype chain.
Key points:
- Every object in JavaScript has a hidden
[[Prototype]]property (accessible via__proto__) - Objects can inherit properties and methods from their prototype
- The prototype chain ends with
Object.prototype, whose prototype isnull Object.create()can be used to create an object with a specific prototype
Example:
// Constructor function
function Animal(name) {
this.name = name;
}
// Adding a method to the prototype
Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound.`);
};
// Creating a new object
const dog = new Animal("Rex");
dog.speak(); // Output: Rex makes a sound.
// Checking the prototype chain
console.log(dog.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // trueDiagram: Prototype Chain
┌─────────┐ ┌──────────────────┐ ┌───────────────────┐ ┌──────┐
│ dog │───▶│ Animal.prototype │───▶│ Object.prototype │───▶│ null │
└─────────┘ └──────────────────┘ └───────────────────┘ └──────┘
Arrow functions, introduced in ES6, provide a more concise syntax for writing function expressions. They differ from regular functions in several important ways.
Key differences:
- Syntax: Arrow functions have a shorter syntax
thisbinding: Arrow functions do not have their ownthis- No
argumentsobject: Arrow functions don't have theargumentsobject - Cannot be used as constructors: Arrow functions cannot be used with the
newkeyword - No
prototypeproperty: Arrow functions don't have aprototypeproperty
Examples:
- Syntax:
// Regular function
function add(a, b) {
return a + b;
}
// Arrow function
const addArrow = (a, b) => a + b;thisbinding:
const obj = {
name: "John",
greet: function() {
setTimeout(function() {
console.log(`Hello, ${this.name}!`); // `this` is not bound to obj
}, 1000);
},
greetArrow: function() {
setTimeout(() => {
console.log(`Hello, ${this.name}!`); // `this` is bound to obj
}, 1000);
}
};
obj.greet(); // Output: Hello, undefined!
obj.greetArrow(); // Output: Hello, John!- No
argumentsobject:
function regular() {
console.log(arguments);
}
const arrow = () => {
console.log(arguments); // ReferenceError: arguments is not defined
};
regular(1, 2, 3); // Output: [1, 2, 3]
arrow(1, 2, 3); // Throws an error- Cannot be used as constructors:
const Person = (name) => {
this.name = name;
};
const john = new Person("John"); // TypeError: Person is not a constructorDiagram: Arrow vs Regular Functions
┌─────────────────────────┐ ┌─────────────────────────┐
│ Regular Function │ │ Arrow Function │
├─────────────────────────┤ ├─────────────────────────┤
│ - Own `this` binding │ │ - No own `this` binding │
│ - Has `arguments` object│ │ - No `arguments` object │
│ - Can be constructor │ │ - Cannot be constructor │
│ - Has `prototype` │ │ - No `prototype` │
└─────────────────────────┘ └─────────────────────────┘
async and await are features introduced in ES2017 to simplify working with Promises and asynchronous code. They provide a more synchronous-looking way to write asynchronous code.
Key points:
asyncfunctions always return a Promiseawaitcan only be used inside anasyncfunctionawaitpauses the execution of theasyncfunction until the Promise is resolved- Error handling can be done using try/catch blocks
Example:
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id === 1) {
resolve({ id: 1, name: "John Doe" });
} else {
reject("User not found");
}
}, 1000);
});
}
async function getUser(id) {
try {
const user = await fetchUser(id);
console.log("User:", user);
} catch (error) {
console.error("Error:", error);
}
}
getUser(1); // Output after 1 second: User: { id: 1, name: "John Doe" }
getUser(2); // Output after 1 second: Error: User not foundDiagram: async/await Flow
┌─────────────────┐
│ async function │
│ ┌─────────────┐│
│ │ await ││
│ │ (pauses) ││
│ └──────┬──────┘│
│ │ │
│ ┌──────▼──────┐│
│ │ continue ││
│ │ execution ││
│ └─────────────┘│
└─────────────────┘
The event loop is a fundamental concept in JavaScript that enables non-blocking, asynchronous execution. It continuously checks the call stack and the callback queue, moving callbacks to the call stack when it's empty.
Key components:
- Call Stack: Where synchronous code execution happens
- Web APIs: Where asynchronous operations are offloaded (in browsers)
- Callback Queue: Where completed asynchronous operations wait
- Microtask Queue: Higher priority queue for Promises and mutation observers
- Event Loop: Checks if the call stack is empty and moves callbacks
Example:
console.log('Start');
setTimeout(() => {
console.log('Timeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('End');
// Output:
// Start
// End
// Promise
// TimeoutDiagram: Event Loop
┌───────────────┐ ┌───────────────┐
│ Call Stack │ │ Web APIs │
│ │ │ (Async ops) │
└───────┬───────┘ └───────┬───────┘
│ │
│ ▼
│ ┌───────────────┐
│ │ Callback Queue│
│ └───────┬───────┘
│ │
│ ┌───────────────┐
│ │Microtask Queue│
│ └───────┬───────┘
│ │
└─────────────────────┘
Event Loop
Template literals, introduced in ES6, provide an easier way to create multiline strings and perform string interpolation.
Key features:
- Enclosed by backticks (`) instead of single or double quotes
- Can span multiple lines without explicit line breaks
- Allow embedded expressions using
${expression} - Support tagged templates for custom string parsing
Examples:
- Basic usage:
const name = "John";
const greeting = `Hello, ${name}!`;
console.log(greeting); // Output: Hello, John!- Multiline strings:
const multiline = `
This is a
multiline
string.
`;
console.log(multiline);
// Output:
// This is a
// multiline
// string.- Expression interpolation:
const a = 5;
const b = 10;
console.log(`Sum: ${a + b}, Product: ${a * b}`);
// Output: Sum: 15, Product: 50- Tagged templates:
function highlight(strings, ...values) {
return strings.reduce((acc, str, i) =>
`${acc}${str}<strong>${values[i] || ''}</strong>`, '');
}
const name = "John";
const age = 30;
const result = highlight`My name is ${name} and I'm ${age} years old.`;
console.log(result);
// Output: My name is <strong>John</strong> and I'm <strong>30</strong> years old.The == (loose equality) and === (strict equality) operators are used for comparison in JavaScript, but they behave differently when comparing values of different types.
Key differences:
==performs type coercion before comparison===does not perform type coercion and requires both value and type to be the same
Examples:
// Loose equality (==)
console.log(5 == "5"); // true (string "5" is coerced to number 5)
console.log(0 == false); // true (false is coerced to number 0)
console.log(null == undefined); // true (special case)
// Strict equality (===)
console.log(5 === "5"); // false (different types)
console.log(0 === false); // false (different types)
console.log(null === undefined); // false (different types)
// More examples
console.log([] == 0); // true ([] is coerced to 0)
console.log([] === 0); // false (different types)
console.log('' == false); // true (both coerced to 0)
console.log('' === false); // false (different types)Best practice: It's generally recommended to use === for comparisons to avoid unexpected type coercion issues.
Diagram: Equality Comparison
┌─────────────────────┐ ┌─────────────────────┐
│ Loose Equality (==) │ │ Strict Equality (===)│
├─────────────────────┤ ├─────────────────────┤
│ 1. Type Coercion │ │ 1. Type Check │
│ 2. Value Comparison │ │ 2. Value Comparison │
└─────────────────────┘ └─────────────────────┘
There are several ways to create objects in JavaScript:
-
Object literal notation:
const obj = { name: "John", age: 30 };
-
Constructor function:
function Person(name, age) { this.name = name; this.age = age; } const john = new Person("John", 30);
-
Object.create() method:
const personProto = { greet: function() { console.log(`Hello, I'm ${this.name}`); } }; const john = Object.create(personProto); john.name = "John";
-
ES6 Class syntax:
class Person { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log(`Hello, I'm ${this.name}`); } } const john = new Person("John", 30);
-
Factory function:
function createPerson(name, age) { return { name, age, greet() { console.log(`Hello, I'm ${this.name}`); } }; } const john = createPerson("John", 30);
Each method has its own use cases and advantages. Object literals are great for simple objects, constructor functions and classes are useful for creating multiple instances with shared methods, Object.create() is powerful for setting up inheritance chains, and factory functions offer encapsulation and private variables.
bind, call, and apply are methods available on function objects in JavaScript. They are used to manipulate the this context of a function and, in the case of call and apply, to invoke the function immediately.
-
bindmethod:- Creates a new function with a fixed
thiscontext - Does not immediately invoke the function
- Can partially apply arguments
const person = { name: "John", greet: function(greeting) { console.log(`${greeting}, I'm ${this.name}`); } }; const greetJohn = person.greet.bind(person,"Hello"); greetJohn(); // Output: Hello, I'm John
- Creates a new function with a fixed
-
callmethod:- Invokes the function immediately with a specified
thiscontext - Accepts arguments individually
function greet(greeting, punctuation) { console.log(`${greeting}, I'm ${this.name}${punctuation}`); } const person = { name: "John" }; greet.call(person, "Hello", "!"); // Output: Hello, I'm John!
- Invokes the function immediately with a specified
-
applymethod:- Similar to
call, but accepts arguments as an array - Invokes the function immediately with a specified
thiscontext
function greet(greeting, punctuation) { console.log(`${greeting}, I'm ${this.name}${punctuation}`); } const person = { name: "John" }; greet.apply(person, ["Hello", "!"]); // Output: Hello, I'm John!
- Similar to
Key differences:
bindcreates a new function, whilecallandapplyinvoke the function immediatelycalltakes arguments individually, whileapplytakes arguments as an array
Use cases:
bind: Creating methods with a fixedthiscontext, partial function applicationcall: Invoking methods in a specific context, borrowing methods from other objectsapply: Similar tocall, but useful when arguments are already in an array or when usingMath.max()/min()with arrays
Diagram: bind, call, and apply
┌─────────────────────────────────────────────────────────────┐
│ Function Manipulation │
├─────────────┬─────────────────────────┬─────────────────────┤
│ bind │ call │ apply │
├─────────────┼─────────────────────────┼─────────────────────┤
│ New function│ Immediate invocation │ Immediate invocation│
│ Fixed `this`│ Specified `this` │ Specified `this` │
│ │ Individual arguments │ Array of arguments│
└─────────────┴─────────────────────────┴─────────────────────┘
Event delegation is a technique in JavaScript where you attach a single event listener to a parent element instead of attaching multiple listeners to individual child elements. This approach leverages event bubbling and can improve performance and simplify dynamic content handling.
Key benefits:
- Improved performance (fewer event listeners)
- Dynamically added elements are automatically handled
- Less memory usage
- Cleaner and more maintainable code
Example:
<ul id="todo-list">
<li>Task 1</li>
<li>Task 2</li>
<li>Task 3</li>
</ul>
<script>
document.getElementById('todo-list').addEventListener('click', function(e) {
if (e.target.tagName === 'LI') {
console.log('Clicked on:', e.target.textContent);
e.target.style.textDecoration = 'line-through';
}
});
</script>In this example, instead of attaching click events to each <li> element, we attach a single listener to the parent <ul>. When a click occurs, we check if the clicked element (e.target) is an <li>, and if so, we handle the event.
Diagram: Event Delegation
┌─────────────────────────────┐
│ Parent Element │
│ ┌───────────────────────┐ │
│ │ Event Listener │ │
│ └───────────┬───────────┘ │
│ ▲ │ ▲ │
│ │ ▼ │ │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Child │ │Child │ │Child │ │
│ └──────┘ └──────┘ └──────┘ │
└─────────────────────────────┘
Event Bubbling