100 JS Interview Questions
100 JS Interview Questions
1. What is JavaScript?
JavaScript is a versatile, high-level, interpreted programming language primarily used to
create interactive and dynamic content on web pages. Developed initially by Netscape in
1995, JavaScript allows for client-side scripting, meaning it runs in the user’s browser to
provide fast, responsive experiences without needing to constantly communicate with a
server. It is one of the core technologies of the World Wide Web, alongside HTML and CSS.
While originally used for front-end development, JavaScript has grown to support server-side
development as well through environments like Node.js, making it a full-stack language.
JavaScript is an essential part of modern web applications and can be used to manipulate the
DOM, handle events, validate forms, and perform animations.
var : Declares a variable globally or locally to the function in which it is declared. This is
the traditional way of declaring variables in JavaScript, but it has some scoping issues in
modern development.
javascript
javascript
const : Also introduced in ES6, const declares a block-scoped constant that cannot be
reassigned after its initial declaration. This is useful for values that do not need to
change, such as configuration values or fixed data.
javascript
const pi = 3.14159;
Each of these keywords has specific use cases, and the choice depends on the variable’s
intended purpose and scope requirements.
Primitive Types:
javascript
let x = 42;
let y = 3.14;
javascript
javascript
5. Undefined: Represents a variable that has been declared but not initialized with a value.
javascript
let result;
javascript
javascript
javascript
let : Has block scope, making it accessible only within the {} block in which it is
defined. Variables declared with let can be updated but not re-declared within the
same scope. This block-level scoping prevents variable collisions and is generally safer to
use than var .
const : Also has block scope and is used to declare constants. A const variable cannot
be re-assigned after its initial declaration. Attempting to change its value will result in an
error. However, if the constant is an object or array, the contents can still be modified.
In summary:
Use let for variables that need to be re-assigned but are restricted to specific blocks.
Avoid var due to its scope issues unless needed for legacy code.
Arrow Functions: A compact syntax introduced in ES6, commonly used for shorter
functions.
javascript
function greet() {
console.log("Hello, World!");
}
2. Function Expression:
javascript
javascript
javascript
function greet(name) {
console.log("Hello, " + name);
}
javascript
function add(a, b) {
return a + b;
}
Example:
javascript
javascript
javascript
javascript
Example:
javascript
let person = {
name: "Alice",
age: 30,
greet: function() {
console.log("Hello, " + this.name);
}
};
1. Dot Notation:
javascript
console.log(person.name); // "Alice"
2. Bracket Notation:
javascript
console.log(person["age"]); // 30
Dot notation is typically preferred for its readability, while bracket notation is helpful when
dealing with property names that contain spaces or are dynamically determined (e.g., using
variables).
== (loose equality): Compares two values for equality after performing type conversion
if necessary. This means == will attempt to convert the operands to the same type
before making the comparison. For example, 5 == "5" would return true because
JavaScript converts the string "5" to the number 5 before comparing.
javascript
=== (strict equality): Compares two values for equality without performing type
conversion. The values must have both the same type and value to return true . For
example, 5 === "5" would return false because the number 5 and the string "5"
are different types.
javascript
It is generally recommended to use === for comparisons to avoid unexpected results due to
type coercion.
javascript
function fetchData(callback) {
setTimeout(() => {
console.log("Data fetched");
callback();
}, 1000);
}
function displayData() {
console.log("Displaying data");
}
Here, displayData is the callback function passed to fetchData and is executed after the
data fetch operation completes.
Global context: In the global scope, this refers to the global object (e.g., window in
browsers).
Object context: When used inside an object method, this refers to the object itself.
Event handlers: In event handlers, this refers to the DOM element that triggered the
event.
Arrow functions: Arrow functions do not have their own this binding. Instead, they
inherit this from their enclosing lexical scope.
Example:
javascript
1. for loop:
javascript
2. for...of loop:
javascript
3. forEach() method:
javascript
javascript
Each method has specific use cases. For example, forEach() is commonly used for
processing elements without returning a new array, while map() is useful when you want to
transform elements into a new array.
Example JSON:
json
{
"name": "Alice",
"age": 30,
"isStudent": false
}
In JavaScript, JSON can be converted to and from JavaScript objects using JSON.parse() and
JSON.stringify() :
Parsing JSON:
javascript
Stringifying JSON:
javascript
1. Number() function:
javascript
javascript
javascript
javascript
Each method has specific uses. parseInt() and parseFloat() are helpful when dealing
with partially numeric strings.
Example:
javascript
Template literals improve readability and simplify the creation of complex strings by allowing
inline variable and expression interpolation.
Example:
javascript
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
Classes also support inheritance using the extends keyword, allowing you to create
subclasses with additional or modified functionality.
Example:
javascript
console.log(x); // undefined
var x = 5;
In this example, var x is hoisted to the top, but the assignment x = 5 is not, so x is
undefined until the assignment occurs. let and const declarations are also hoisted but
are not initialized, so accessing them before declaration results in a ReferenceError .
Example:
javascript
setTimeout(function() {
console.log("Hello after 1 second");
}, 1000);
In this example, an anonymous function is passed to setTimeout and executed after one
second. Arrow functions, commonly used for callbacks, are often anonymous:
javascript
Here are detailed explanations of the advanced JavaScript concepts you asked about:
2. Fulfilled: The operation completed successfully, and the promise now holds a result.
3. Rejected: The operation failed, and the promise holds an error reason.
Example:
javascript
myPromise
.then((value) => console.log(value)) // Output: "Success!" after 1 second
.catch((error) => console.error(error));
In this example, the promise is fulfilled after 1 second with "Success!" and the then()
method handles the resolved value. The catch() method would handle any rejection,
allowing for more manageable error handling in asynchronous code.
Example:
javascript
function createCounter() {
let count = 0; // count is a private variable
return function() {
count++;
return count;
};
In this example, count remains accessible to the returned function even after
createCounter has executed, demonstrating how closures capture variables from their
surrounding scope.
Example:
javascript
try {
// Code that may throw an error
let result = riskyFunction();
console.log(result);
} catch (error) {
console.error("An error occurred:", error.message);
} finally {
console.log("Execution complete.");
}
The try block contains the code that may throw an error, catch handles any exceptions,
and finally executes regardless of whether an error occurred. For promises, catch()
handles errors, while async/await syntax uses try...catch for asynchronous error
handling.
The event loop checks the call stack for tasks to execute. If the stack is empty, it moves tasks
from the callback queue to the call stack, processing them one by one. This process allows
JavaScript to handle asynchronous events without blocking other code execution.
javascript
console.log("Start");
let result = longTask(); // Blocks until longTask completes
console.log("End");
javascript
console.log("Start");
setTimeout(() => console.log("Long task complete"), 1000); // Non-blocking
console.log("End");
Example (module.js):
javascript
// Exporting a function
export function greet(name) {
return `Hello, ${name}!`;
}
// Exporting a variable
export const pi = 3.14159;
Example (main.js):
javascript
By using modules, developers can manage dependencies better and keep code modular.
Example:
javascript
let person = {
name: "Alice",
greet: function() {
console.log("Hello, " + this.name);
}
};
Using bind() ensures that this refers to the intended object, preserving context in
asynchronous or callback situations.
Example:
javascript
Person.prototype.greet = function() {
console.log("Hello, " + this.name);
};
Here, greet is defined on Person.prototype , so all instances of Person can access it,
showcasing prototype inheritance.
map() : Creates a new array by applying a function to each element of the original array.
javascript
filter() : Creates a new array with only the elements that pass a specified condition.
javascript
javascript
Each of these methods enables powerful data transformations with concise code.
1. Using closures:
javascript
function Counter() {
let count = 0; // Private variable
this.increment = function() {
count++;
return count;
};
}
javascript
class Counter {
#count = 0; // Private field
increment() {
this.#count++;
return this.#count;
}
}
Private variables are not accessible outside the containing function or class, ensuring
encapsulation and data privacy.
Examples:
javascript
One caveat is that typeof null returns "object" instead of "null" , which is due to a
historical issue in JavaScript. The typeof operator is often used for debugging and to check
variables before performing operations on them.
Examples:
javascript
let x;
console.log(x); // undefined
x = null;
console.log(x); // null
While both null and undefined indicate the lack of value, null is explicitly set, while
undefined is implicitly assigned by JavaScript.
javascript
javascript
arr.shift(); // [2, 3]
javascript
let newArr = arr.filter(item => item !== 2); // Removes all instances of 2
Each method serves a specific purpose, depending on whether you want to alter the
beginning, end, or middle of the array.
map() , filter() , and reduce() are higher-order functions because they accept
callback functions that specify how each element should be processed.
Example:
javascript
function greet(name) {
return "Hello, " + name;
}
Higher-order functions are widely used for abstracting behaviors and implementing
common programming patterns like currying and partial application.
await : Pauses the execution of an async function until the promise is resolved or
rejected. It can only be used inside an async function.
Example:
javascript
fetchData();
In this example, await pauses the function execution until the fetch request and JSON
parsing complete, making it easier to handle asynchronous code than traditional promises.
Examples:
1. Array merging:
2. Object copying:
javascript
let obj1 = { a: 1, b: 2 };
let obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
3. Function arguments:
javascript
function sum(x, y, z) {
return x + y + z;
}
let numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6
The spread operator enhances the flexibility of handling data and provides a concise syntax
for operations that previously required multiple steps.
Example:
javascript
class Person {
constructor(name, age) {
this.name = name;
Here, new Person("Alice", 25) creates an instance of Person with the provided name and
age properties.
javascript
javascript
apply() is helpful when you already have an array of arguments, while call() is typically
used when arguments are provided directly.
javascript
if (typeof obj1 !== "object" || typeof obj2 !== "object" || obj1 == null || obj2
== null) return false;
return true;
}
The deepEqual function recursively checks if each property and value is identical in both
objects.
javascript
(function() {
let message = "Hello, world!";
console.log(message);
})(); // Output: "Hello, world!"
javascript
(() => {
console.log("IIFE with arrow function");
})();
IIFEs were particularly useful before ES6 introduced let and const for block-scoped
variables, and they remain useful for encapsulating code.
These concepts provide a deeper understanding of JavaScript's flexibility and power for
building more complex applications.
Here are detailed explanations for each of these advanced JavaScript concepts:
Example:
In this example, rabbit inherits the eats property from animal because animal is the
prototype of rabbit . This chain of prototypes forms the foundation of inheritance in
JavaScript.
javascript
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
In this example, Dog inherits from Animal , so Dog instances can access properties and
methods from Animal . Inheritance using class syntax is simpler and more readable
compared to using prototypes directly.
4. Returns the new object unless the constructor explicitly returns an object.
Example:
javascript
Here, new Person("Alice", 25) creates an instance of Person , setting this.name and
this.age on the new object.
Variables and objects that go out of scope or have no references are eligible for garbage
collection. This process helps avoid memory leaks, though it can sometimes result in
performance issues if unused references persist.
Example:
javascript
function createUser() {
let user = { name: "Alice" };
return user;
}
Here, setting user1 to null makes the original user object eligible for garbage collection
since there are no remaining references to it.
javascript
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js")
.then(reg => console.log("Service worker registered:", reg))
.catch(err => console.error("Registration failed:", err));
}
Once registered, the service worker in sw.js can intercept network requests and manage
cached resources.
Debouncing delays the function execution until a specified time has passed since the
last event. It is useful for reducing the frequency of function calls on events like typing or
window resizing.
javascript
Throttling allows a function to run at most once every specified time interval, ensuring it
runs at regular intervals. This is helpful for scroll events and resizing where frequent
updates could hurt performance.
javascript
javascript
let original = { a: 1, b: { c: 2 } };
let clone = JSON.parse(JSON.stringify(original));
clone.b.c = 3;
console.log(original.b.c); // Output: 2 (unchanged)
For more complex structures, such as those with functions, consider using libraries like
lodash with _.cloneDeep() .
48. What is the Symbol type, and when would you use it?
Example:
javascript
console.log(obj[sym1]); // "value"
Symbols are also used for creating private fields in objects and classes, and for well-known
symbols (like Symbol.iterator ), which add custom behaviors to objects.
49. Explain the difference between shallow copy and deep copy.
Shallow copy: Copies only the first level of properties. If a property is a reference (e.g.,
an object), the reference itself is copied, not the object it points to.
javascript
let original = { a: 1, b: { c: 2 } };
let shallowCopy = { ...original };
shallowCopy.b.c = 3;
console.log(original.b.c); // 3 (changed in both copies)
Deep copy: Copies all levels of an object, including nested objects, creating an entirely
separate object structure.
javascript
let original = { a: 1, b: { c: 2 } };
let deepCopy = JSON.parse(JSON.stringify(original));
Deep copies are often used when working with complex or nested data structures, ensuring
that changes to the copy do not affect the original.
1. Minimize DOM manipulation: Use techniques like batching DOM updates or using
virtual DOM libraries (e.g., React) to minimize costly DOM operations.
2. Debounce and throttle events: Apply debouncing and throttling to manage high-
frequency events, such as scrolling and resizing.
3. Optimize loops and computations: Use efficient algorithms, cache results of expensive
calculations, and prefer built-in array methods (e.g., map , filter ) for better readability
and speed.
4. Lazy loading: Load assets like images and modules only when needed, using techniques
like lazy loading for better load times and resource management.
5. Avoid memory leaks: Use tools like Chrome DevTools to monitor memory usage, and
remove unused variables, event listeners, and timers.
6. Reduce HTTP requests: Minimize the number of HTTP requests by bundling and
compressing files (e.g., CSS and JavaScript).
7. Use asynchronous code wisely: Implement async and await to handle I/O operations,
so the main thread remains responsive.
8. Minify code: Remove unnecessary whitespace, comments, and unused code before
deploying.
A web worker is created using the Worker constructor, which takes a JavaScript file as input:
javascript
// main.js
const worker = new Worker("worker.js");
javascript
// worker.js
onmessage = (event) => {
console.log("Message from main thread:", event.data);
postMessage("Hello from worker thread!");
};
Limitations:
They have limited access to certain global objects and functions (e.g., no window object).
Example:
javascript
console.log("Start");
setTimeout(() => {
console.log("Executed after 2 seconds");
}, 2000);
console.log("End");
Output:
sql
Start
End
Executed after 2 seconds
In this example, "Start" and "End" are printed immediately, while "Executed after 2 seconds"
is printed after a 2-second delay. Even though setTimeout schedules the callback for later, it
doesn’t pause or block the execution of subsequent code.
Example:
javascript
function createCounter() {
let count = 0;
return function () {
return ++count;
};
}
Here, the returned function retains access to the count variable, forming a closure.
Memory leaks can occur with closures if variables that are no longer needed remain
referenced and thus cannot be garbage-collected. This can happen if event listeners, timers,
or callbacks hold references to variables in closures after they are no longer needed.
javascript
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
this.value = Math.random();
getValue() {
return this.value;
}
}
Here, Singleton.instance holds the single instance of the Singleton class. Each time new
Singleton() is called, it returns the same instance.
Example:
javascript
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
get area() {
return this.width * this.height;
}
set area(value) {
this.width = Math.sqrt(value);
this.height = Math.sqrt(value);
}
Here, area is a getter that calculates the rectangle's area, while the setter allows us to
change width and height based on a new area.
Example:
javascript
function privateMethod() {
return "I'm private";
}
return {
publicMethod: function () {
return privateMethod() + " and accessible through publicMethod";
},
increment: function () {
return ++privateVariable;
}
};
})();
This pattern helps organize code into reusable modules while controlling the visibility of
specific parts of code.
Callback functions are passed as arguments to other functions to execute after a specific
action or event occurs. Unlike event emitters, callbacks are invoked directly by the function
calling them, limiting them to single use.
Basic usage:
javascript
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then(response => {
if (!response.ok) throw new Error("Network response was not ok");
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error("Fetch error:", error));
Example:
javascript
let handler = {
get: function (obj, prop) {
return prop in obj ? obj[prop] : `Property ${prop} not found`;
},
set: function (obj, prop, value) {
if (prop === "age" && typeof value !== "number") {
throw new Error("Age must be a number");
}
obj[prop] = value;
}
};
console.log(proxy.name); // Alice
proxy.age = 30;
console.log(proxy.age); // 30
// proxy.age = "thirty"; // Error: Age must be a number
Here, the Proxy validates the age property and intercepts get operations to provide
custom behavior.
Basic syntax (using TypeScript as JavaScript does not yet fully support decorators natively):
typescript
class Person {
@ReadOnly
name() {
return "Alice";
}
}
The @ReadOnly decorator makes the name method read-only, preventing it from being
reassigned. This technique enables code modularity and simplifies modifications to specific
properties or methods without changing the entire implementation.
These advanced concepts showcase JavaScript's versatility and the variety of techniques
available to solve complex problems efficiently.
Here are detailed answers to the advanced JavaScript concepts you requested:
javascript
2. Pure Functions: Functions that, given the same input, will always return the same
output without causing side effects. This makes them easier to test and reason about.
javascript
3. Higher-Order Functions: Functions that can take other functions as arguments or return
them. They are commonly used for callbacks and transformations.
javascript
4. Immutability: Data should not be modified directly. Instead, new data structures are
created, which can lead to more predictable and less error-prone code.
javascript
javascript
javascript
class Observable {
constructor() {
this.observers = []; // List of observers
}
subscribe(observer) {
this.observers.push(observer); // Add observer to the list
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer); // Remove
observer
}
notify(data) {
this.observers.forEach(observer => observer.update(data)); // Notify all
observers
}
}
class Observer {
update(data) {
console.log(`Observer received data: ${data}`);
}
}
observable.subscribe(observer1);
observable.subscribe(observer2);
In this example, the Observable class manages a list of Observer instances. When notify
is called, all subscribed observers are informed of the update.
63. What are weak references, and when would you use them?
Weak references in JavaScript are created using the WeakRef constructor. They allow you to
hold a reference to an object without preventing it from being garbage collected. This is
useful in scenarios where you want to track an object but don’t want to affect its lifetime,
thus avoiding memory leaks.
javascript
Use Cases:
Event listeners: Weak references can help in managing listeners that you want to
remove automatically when the target object is collected, thus preventing memory leaks.
1. State Management Libraries: Use libraries like Redux, MobX, or Vuex (for Vue.js) that
provide centralized state management, allowing for predictable state transitions and
easier debugging.
2. Context API: In React applications, use the Context API to manage state across
components without prop drilling. This helps maintain a cleaner component hierarchy.
3. Local State: Manage component-level state using local state management within
components for simpler or isolated parts of the application.
4. Immutable Data Structures: Use libraries like Immutable.js or immer.js to ensure that
state changes are managed immutably, preventing unintended side effects.
6. Hooks: In React, use hooks like useReducer for more complex state logic while keeping
components clean and functional.
7. Single Source of Truth: Ensure that your application has a single source of truth for the
state. This can be achieved using a central state store, reducing redundancy and
inconsistency.
8. Data Fetching Libraries: Libraries like Axios or React Query can help manage server
state and caching, streamlining the process of retrieving and updating data.
By employing these strategies, you can effectively manage state and maintain a clear
structure as your JavaScript application scales.
javascript
const target = {
name: "Alice",
};
const handler = {
get(obj, prop) {
console.log(`Getting property ${prop}`);
return Reflect.get(obj, prop); // Use Reflect to get property value
},
set(obj, prop, value) {
console.log(`Setting property ${prop} to ${value}`);
return Reflect.set(obj, prop, value); // Use Reflect to set property value
}
};
javascript
// Usage
try {
throw new CustomError("This is a custom error message.");
} catch (error) {
console.error(`${error.name}: ${error.message}`); // Output: CustomError: This is
a custom error message.
console.error(error.stack); // Print the stack trace
}
Benefits:
Allows for more specific error handling based on the custom error type.
javascript
const vDOM = {
tag: 'div',
props: { id: 'app' },
children: [
{ tag: 'h1', props: {}, children: ['Hello, World!'] },
{ tag: 'p', props: {}, children: ['This is a virtual DOM.'] }
]
};
2. Rendering the Virtual DOM: Convert the virtual DOM into actual DOM elements and
append them to the document.
javascript
function createElement(vNode) {
const el = document.createElement(vNode.tag);
Object.entries(vNode.props).forEach(([key, value]) => {
el.setAttribute(key, value);
});
vNode.children.forEach(child => {
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
el.appendChild(createElement(child));
}
});
return el;
}
3. Diffing Algorithm: When the state changes, generate a new virtual DOM tree and
compare it with the previous one. Identify what changed and update only those parts of
the actual DOM, avoiding full re-renders.
javascript
4. Batch Updates: Update the real DOM in batches to improve performance, minimizing
the number of manipulations required.
By using a virtual DOM, applications can achieve high performance and responsiveness, as
updates are more efficient and involve fewer direct DOM manipulations.
javascript
In this example, factorial calls itself as its last operation, enabling TCO. If TCO is applied, it
can prevent the stack from growing with each recursive call.
As of now, JavaScript does not officially support tail call optimization in all environments.
However, some engines, like Safari, do implement TCO. Thus, developers should be
cautious when relying on it across different environments.
How it works:
1. Event Bubbling: Events in JavaScript bubble up from the target element to its ancestors.
Event delegation takes advantage of this behavior.
2. Attach Listener to Parent: Instead of adding event listeners to each child, you add one
listener to the parent element.
3. Handle Events in One Place: Inside the event handler, check the event target to
determine which child element was clicked.
Example:
javascript
Benefits:
Dynamic Elements: Automatically applies to elements added to the DOM after the
listener was attached.
Key features:
2. for await...of Loop: This loop is used to iterate over async iterators, pausing
execution until each promise resolves.
Example:
javascript
(async () => {
Use Cases:
Handling Streams: Processing streams of data from sources like file I/O, WebSockets, or
APIs.
Async iterators provide a powerful abstraction for dealing with asynchronous data flow in a
clean and maintainable way, promoting better coding practices in JavaScript.
These advanced concepts illustrate JavaScript's flexibility and depth, showcasing how to
effectively use the language in various scenarios. If you have any further questions or need
clarification on any of these topics, feel free to ask!
Here are detailed answers to the advanced JavaScript concepts you requested:
Event Listeners: Not removing event listeners can prevent objects from being
garbage collected. Use removeEventListener when they're no longer needed.
Closures: Careful with closures that capture large objects; if not properly managed,
they can hold references unnecessarily.
3. Weak References: Utilize WeakMap or WeakSet for collections of objects that should not
prevent garbage collection. Objects referenced in these collections can be garbage
collected when no other references exist.
4. Profiling and Debugging: Use developer tools to monitor memory usage, inspect
memory leaks, and identify large allocations. Tools like Chrome DevTools provide a
memory tab for profiling.
72. What are some ways to improve the loading time of a web page?
Improving the loading time of a web page is crucial for user experience and SEO. Here are
several strategies to enhance loading performance:
1. Optimize Assets:
2. Image Optimization:
Use modern image formats (like WebP) that offer better compression without
sacrificing quality.
Resize images to the appropriate dimensions for display and use responsive images
with the srcset attribute.
4. Content Delivery Network (CDN): Serve static assets from a CDN to reduce latency.
CDNs distribute content across multiple servers globally, decreasing the distance
between users and the server.
5. Lazy Loading: Load images and other resources only when they are visible in the
viewport. This technique reduces initial load time and improves user experience.
6. Reduce HTTP Requests: Combine CSS and JavaScript files to minimize the number of
requests. Use CSS sprites to combine multiple images into one.
7. Asynchronous Loading of Scripts: Use the async or defer attributes on script tags to
prevent blocking the rendering of the page.
html
8. Optimize CSS and JavaScript: Place critical CSS inline to reduce render-blocking and
defer non-critical styles. Consider splitting JavaScript into smaller bundles to improve
initial loading time.
9. Server-Side Rendering (SSR): If using frameworks like React, consider implementing SSR
to deliver fully rendered pages to users, improving perceived load times.
10. Monitoring and Testing: Regularly monitor page performance using tools like Google
Lighthouse, GTmetrix, or WebPageTest to identify bottlenecks and areas for
improvement.
By implementing these strategies, developers can significantly enhance the loading times of
web pages, leading to a better user experience and increased engagement.
1. Parsing: The engine begins by parsing the JavaScript code. The parser converts the
source code into an Abstract Syntax Tree (AST), a structured representation of the code's
syntax.
Optimization: As the bytecode runs, the engine collects profiling information about
frequently executed code (hot paths). The JIT compiler can optimize this code
further, generating native machine code for improved performance.
3. Execution: The engine executes the bytecode or optimized machine code. During
execution, it manages the call stack, handles variable scope, and executes functions.
4. Garbage Collection: The engine periodically runs garbage collection to reclaim memory
used by objects that are no longer referenced. This process helps prevent memory leaks.
5. Event Loop: JavaScript engines use an event loop to manage asynchronous operations.
The event loop allows the engine to handle multiple tasks (like user input, timers, or
network requests) by executing callbacks in response to events.
6. API Integration: JavaScript engines integrate with web APIs (like the DOM, Fetch API,
etc.) provided by the browser. When JavaScript code interacts with these APIs, the engine
communicates with the underlying browser infrastructure to perform actions like
updating the DOM or making HTTP requests.
The combination of parsing, compilation, execution, and garbage collection allows JavaScript
engines to execute code efficiently, providing a responsive experience for users.
javascript
function memoize(fn) {
const cache = {}; // Create an object to store cached results
return function(...args) {
const key = JSON.stringify(args); // Create a cache key based on arguments
if (cache[key]) {
return cache[key]; // Return cached result if available
}
const result = fn(...args); // Call the original function
cache[key] = result; // Cache the result
return result; // Return the computed result
};
}
// Example usage
const factorial = memoize(function(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
});
In this example:
The memoize function creates a cache object to store results keyed by serialized
arguments.
When the returned function is called, it checks if the result for those arguments is
already cached.
If it is, it returns the cached value; if not, it computes the result, caches it, and returns it.
1. Variable Declarations:
ES6: Introduces let and const for block-scoped variable declarations, allowing for
better control over variable scope.
javascript
2. Arrow Functions:
ES6: Introduces arrow functions, which provide a more concise syntax and lexical
this binding.
javascript
3. Template Literals:
ES6: Introduces template literals, allowing for multi-line strings and string
interpolation using backticks.
javascript
4. Destructuring Assignment:
ES6: Introduces destructuring assignment for arrays and objects, making it easier to
extract values.
javascript
5. Default Parameters:
javascript
function multiply(a, b = 1) {
return a * b; // Default value for b
}
6. Classes:
ES6: Introduces class syntax, making it easier to create and extend objects.
javascript
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
ES5: Does not have a native module system; often relies on IIFE or third-party
libraries (like CommonJS or AMD).
javascript
// In module.js
export const pi = 3.14;
// In another file
import { pi } from './module.js';
8. Promises:
ES6: Introduces Promises for better handling of asynchronous code, allowing for
chaining and improved readability.
javascript
These features make ES6 a significant enhancement over ES5, providing a more powerful
and expressive syntax for modern JavaScript development.
1. Base Case: The condition under which the recursion stops. This prevents infinite loops
and ensures the function eventually returns a value.
2. Recursive Case: The part of the function that calls itself with different arguments,
moving towards the base case.
javascript
function factorial(n) {
if (n <= 1) return 1; // Base case
return n * factorial(n - 1); // Recursive case
}
In this example:
Advantages of Recursion:
Expressiveness: Recursion can express problems that are naturally recursive, such as
tree traversal or combinatorial problems.
Disadvantages of Recursion:
Performance: Recursive functions can lead to higher memory usage due to the call
stack, especially if the recursion depth is large.
Stack Overflow: Excessively deep recursion can cause a stack overflow error. Iterative
solutions may be more efficient for certain problems.
When using recursion, it’s essential to ensure that a base case exists and that the recursive
case progresses towards it to avoid infinite loops.
Creating Middleware:
2. Perform the desired operation (like modifying the request, logging, etc.).
javascript
// Middleware function
const logger = (req, res, next) => {
console.log(`${req.method} request for '${req.url}'`);
next(); // Call next middleware
};
app.listen(3000, () => {
console.log('Server running on port 3000');
});
In this example:
The logger middleware logs the HTTP method and request URL to the console.
After logging, it calls next() to proceed to the next middleware or route handler.
Types of Middleware:
3. Error Handling Middleware: Middleware that has four arguments: err , req , res , and
next , used to handle errors in the application.
4. Built-in Middleware: Express has built-in middleware functions like express.json() for
parsing JSON requests.
Middleware functions provide a flexible way to extend the functionality of web applications
and manage the request-response cycle efficiently.
Key Components:
1. States: The various conditions or configurations that the machine can be in.
2. Transitions: The rules that define how the machine moves from one state to another,
often triggered by events.
Example Implementation:
javascript
transition(event) {
switch (this.state) {
case 'idle':
if (event === 'start') {
this.state = 'running';
console.log('Transitioning to running state');
}
break;
case 'running':
if (event === 'pause') {
this.state = 'paused';
console.log('Transitioning to paused state');
} else if (event === 'stop') {
this.state = 'idle';
console.log('Transitioning to idle state');
}
break;
case 'paused':
if (event === 'resume') {
this.state = 'running';
console.log('Transitioning to running state');
} else if (event === 'stop') {
this.state = 'idle';
console.log('Transitioning to idle state');
}
break;
default:
console.log('Unknown state');
}
}
}
// Example usage
const fsm = new StateMachine();
fsm.transition('start'); // Transitioning to running state
fsm.transition('pause'); // Transitioning to paused state
In this example:
State machines are particularly useful in applications with complex states and behaviors,
such as user interfaces, animations, and network protocols.
Implementation:
1. Node Class: Represents each node in the linked list, containing data and a reference to
the next node.
2. LinkedList Class: Manages the linked list, including operations like adding, removing,
and searching for nodes.
Example Implementation:
javascript
class Node {
constructor(data) {
class LinkedList {
constructor() {
this.head = null; // First node in the list
}
// Example usage
const list = new LinkedList();
list.append(1);
list.append(2);
list.append(3);
list.display(); // Output: 1 -> 2 -> 3
list.remove(2);
list.display(); // Output: 1 -> 3
In this example:
The Node class represents each node with data and a pointer to the next node.
The LinkedList class manages the linked list, with methods to append, display, and
remove nodes.
The append method adds a new node to the end of the list, and the remove method
removes a node by its value.
Dynamic Size: Unlike arrays, linked lists can grow and shrink in size dynamically.
Linked lists are useful for applications where frequent insertions and deletions are required,
such as implementing queues, stacks, or adjacency lists in graphs.
Types of Coercion:
javascript
javascript
2. Explicit Coercion: Developers can explicitly convert types using functions like String() ,
Number() , or Boolean() .
javascript
console.log(Number('5')); // Output: 5
console.log(String(5)); // Output: "5"
console.log(Boolean(0)); // Output: false
javascript
Benefits: Coercion allows for flexible and dynamic code, enabling different types to
interact.
Drawbacks: Implicit coercion can lead to unexpected results and bugs. It’s crucial to be
aware of type conversions to avoid confusion.
In general, it’s recommended to use strict equality ( === ) to avoid unintentional type
coercion and ensure that comparisons are made based on both value and type.
These detailed answers cover a wide range of advanced JavaScript concepts and can serve as
a valuable resource for learning and understanding JavaScript. If you have more questions or
need clarification on any topic, feel free to ask!
Here are detailed explanations for each of your questions about advanced JavaScript
concepts:
1. Using Mixins
Mixins are a way to include properties and methods from one or more objects into another
object or class. You can create a function that takes multiple objects and merges them.
Example:
javascript
const canFly = {
fly() {
console.log("Flying!");
}
};
const canSwim = {
swim() {
const bird = {
chirp() {
console.log("Chirping!");
}
};
// Mixin function
const mixin = (target, ...sources) => {
Object.assign(target, ...sources);
};
In this example, the mixin function takes multiple source objects ( canFly , canSwim ) and
adds their properties and methods to the target object ( bird ).
2. Using Composition
Instead of inheritance, you can create classes that utilize instances of other classes.
Example:
javascript
class Flyer {
fly() {
console.log("Flying!");
}
}
class Swimmer {
swim() {
console.log("Swimming!");
}
}
chirp() {
console.log("Chirping!");
}
}
Here, the Bird class uses instances of Flyer and Swimmer classes, allowing it to access
their methods without needing to inherit from them directly.
Syntax:
javascript
tagFunction`template string`;
The tagFunction is invoked with the template literal, and the parts of the template are
passed as arguments.
Example:
javascript
In this example, the highlight function wraps interpolated values in <strong> tags. This
demonstrates how tagged template literals can manipulate the output of template strings.
Steps to Use:
2. Observe Targets: Call the observe method on the observer instance with the target
element.
Example:
javascript
In this example:
The callback function checks if the element is intersecting and logs it.
Each target element with the class .lazy-load is observed, and actions can be taken
when they come into view.
Implementation:
You can implement a simple pub/sub system using an object to manage subscribers and
publishers.
Example:
javascript
class PubSub {
constructor() {
this.subscribers = {};
}
subscribe(event, callback) {
if (!this.subscribers[event]) {
this.subscribers[event] = [];
}
this.subscribers[event].push(callback);
}
publish(event, data) {
unsubscribe(event, callback) {
if (!this.subscribers[event]) return;
// Usage
const pubsub = new PubSub();
In this implementation:
The PubSub class manages subscribers and allows them to subscribe to events, publish
events, and unsubscribe.
1. Static Typing: TypeScript allows developers to define types for variables, function
parameters, and return values. This helps catch errors at compile-time rather than
runtime.
typescript
2. Improved Tooling: IDEs and text editors provide better autocompletion, navigation, and
refactoring capabilities for TypeScript due to the presence of type information.
3. Enhanced Readability: The explicit type definitions improve code readability, making it
easier for developers to understand how to use functions and classes.
4. Interfaces and Type Aliases: TypeScript allows the creation of interfaces and type
aliases, enabling better design patterns and data structures.
typescript
interface User {
name: string;
age: number;
}
5. Support for Modern JavaScript Features: TypeScript compiles down to plain JavaScript,
allowing developers to use the latest features of JavaScript while ensuring compatibility
with older browsers.
6. Namespaces and Modules: TypeScript provides better support for organizing code with
namespaces and modules, improving code organization in large applications.
7. Error Handling: TypeScript can catch many common errors during development,
reducing bugs in production code.
Using TypeScript helps improve the overall quality and maintainability of code, especially in
large applications with multiple developers.
86. How can you prevent a function from being called multiple times?
To prevent a function from being called multiple times, you can use a technique called
debouncing or implement a flag that keeps track of whether the function is already
executing.
1. Using a Flag:
You can define a variable that acts as a flag to indicate whether the function is currently
being executed.
Example:
function executeOnce() {
if (isExecuting) return;
isExecuting = true;
// Simulate a task
console.log("Function is executing...");
In this example, the function will only execute if isExecuting is false. After the function
executes, it sets the flag to true and resets it after a timeout.
2. Using Debouncing:
Debouncing ensures that a function is executed only after a specified period of inactivity.
Example:
javascript
// Usage
const saveInput = debounce((value) => {
saveInput("Hello");
saveInput("World"); // Only "Saving: World" will be printed after 1 second.
In this example, the debounce function ensures that the saveInput function is only called
after 1 second of inactivity.
1. ArrayBuffer:
An ArrayBuffer is a generic, fixed-length binary data buffer. It can hold raw binary data and
is used as a container for typed arrays.
Creating an ArrayBuffer:
javascript
2. TypedArray:
Typed arrays are views that provide a way to read and write data in an ArrayBuffer . They
represent an array of a specific data type, such as Int8Array , Uint8Array , Float32Array ,
etc.
Example:
javascript
Performance: Typed arrays allow for efficient manipulation of binary data, making them
suitable for performance-critical applications like WebGL and audio processing.
Interoperability: They can be used with various APIs that require binary data, such as
the fetch API to handle Blob and ArrayBuffer responses.
Implementation:
You can create a binary tree using a Node class to represent each node and a BinaryTree
class to manage the tree.
Example:
javascript
class Node {
constructor(value) {
this.value = value;
this.left = null; // Left child
this.right = null; // Right child
}
}
class BinaryTree {
constructor() {
this.root = null; // Root of the tree
}
// Usage
const tree = new BinaryTree();
tree.insert(10);
tree.insert(5);
tree.insert(15);
tree.inOrder(); // Output: 5, 10, 15
In this implementation:
The Node class represents each node of the binary tree with a value and pointers to left
and right children.
4. Limited Multithreading Support: While modern JavaScript supports web workers for
background tasks, it does not provide native multithreading capabilities. This limits the
ability to perform multiple operations in parallel efficiently.
5. Execution Speed: JavaScript may not perform as quickly as compiled languages (e.g., C,
C++) for CPU-intensive tasks. Although JavaScript engines have become highly
optimized, certain tasks are still better suited for other languages.
6. Security Restrictions: Cross-Origin Resource Sharing (CORS) policies can restrict how
JavaScript interacts with resources from different domains, potentially complicating API
requests and data sharing.
7. Memory Management: While JavaScript has automatic garbage collection, it can lead to
memory leaks if references to objects are unintentionally maintained, causing increased
memory usage and performance issues over time.
8. Limited Access to APIs: Although the browser environment provides various APIs (like
the DOM, Canvas, WebGL, etc.), it may lack certain capabilities found in Node.js or other
server-side environments.
Implementation:
You can use the CustomEvent constructor to create a custom event and the dispatchEvent
method to trigger it.
Example:
javascript
In this example:
A custom event named myCustomEvent is created with some additional data ( detail ).
The event is dispatched, and when it occurs, the listener outputs the message contained
in the event's details.
Using custom events can enhance the modularity of your code and allow different
components to communicate effectively without direct dependencies.
If you have any further questions or need more information on these topics, feel free to ask!
Here are detailed explanations for your questions regarding advanced JavaScript concepts:
Key Points:
1. Async Functions: Any function declared with the async keyword returns a promise,
regardless of whether it contains an explicit return statement. If the function returns a
value, that value is wrapped in a resolved promise. If it throws an error, the promise is
rejected.
javascript
2. Await Keyword: Inside an async function, you can use the await keyword before a
promise. The execution of the async function will pause until the promise is resolved or
rejected, allowing for a cleaner and more linear code flow.
javascript
3. Error Handling: You can use try / catch blocks to handle errors when using await ,
which simplifies error management compared to handling promise rejections with
.catch() .
javascript
Overall, async / await improves code clarity and reduces nesting, making it easier to read
and maintain.
Implementation:
You can implement a function that recursively checks each property of the objects or arrays.
Example:
javascript
// Usage
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };
const obj3 = { a: 1, b: { c: 3 } };
In this implementation:
The function first checks if the two arguments are the same reference.
If they are not the same, it checks if both arguments are objects. If either is not, it
returns false .
It then checks if the objects have the same number of properties and recursively
compares each property for equality.
1. Cross-Site Scripting (XSS): Attackers can inject malicious scripts into web pages viewed
by other users. If the application does not sanitize user input, these scripts can execute
in the context of the victim's browser.
Prevention: Sanitize and escape user input, use Content Security Policy (CSP)
headers, and validate data on both the client and server sides.
2. Cross-Site Request Forgery (CSRF): Attackers can trick a user into executing unwanted
actions on a different site where they are authenticated. For example, submitting a form
without the user's consent.
Prevention: Use anti-CSRF tokens, require user authentication for sensitive actions,
and verify the origin of requests.
Prevention: Encrypt sensitive data, use HTTP-only cookies for sensitive sessions, and
avoid storing sensitive information in client-side storage.
5. Denial of Service (DoS): Attackers can attempt to overwhelm a web application with
excessive requests or long-running scripts, causing performance degradation or service
unavailability.
Techniques:
1. Window Resize Events: Use JavaScript to listen for window resize events and adjust
elements or styles accordingly.
Example:
javascript
window.addEventListener('resize', () => {
const width = window.innerWidth;
if (width < 600) {
document.body.classList.add('mobile');
} else {
document.body.classList.remove('mobile');
}
});
2. Dynamic Styling: Modify CSS properties using JavaScript based on screen size or other
conditions.
javascript
function updateLayout() {
const container = document.getElementById('container');
if (window.innerWidth < 600) {
container.style.flexDirection = 'column';
} else {
container.style.flexDirection = 'row';
}
}
window.addEventListener('resize', updateLayout);
updateLayout(); // Initial call
3. Media Queries: Use CSS media queries to set different styles for various screen sizes.
You can also dynamically add or remove styles using JavaScript.
Example:
javascript
Example:
javascript
function toggleVisibility() {
const button = document.getElementById('myButton');
if (window.innerWidth < 400) {
button.style.display = 'none';
window.addEventListener('resize', toggleVisibility);
toggleVisibility(); // Initial call
1. Weak References: The keys in a WeakMap are weakly held, meaning that if there are no
other references to the key object, it can be garbage collected. This prevents memory
leaks that can occur with regular maps when used with objects.
2. Non-iterable: WeakMaps do not support iteration. You cannot retrieve keys, values, or
entries using methods like .keys() , .values() , or .entries() .
Usage Example:
javascript
Key Types: In WeakMap , keys must be objects, whereas in Map , keys can be any value
(objects, primitives, etc.).
Iteration: Map can be iterated, allowing access to its keys and values, whereas WeakMap
does not provide such methods.
96. How can you detect if a user is online or offline using JavaScript?
You can use the navigator.onLine property along with the online and offline events to
detect the network status of the user in a web application.
Implementation:
1. Checking Initial Status: Use navigator.onLine to check if the user is currently online or
offline.
2. Listening for Events: Add event listeners for the online and offline events to
respond to changes in network status.
Example:
javascript
function updateOnlineStatus() {
const status = navigator.onLine ? "Online" : "Offline";
console.log(status);
}
In this example, updateOnlineStatus is called to log the current status to the console. It is
also triggered whenever the network status changes.
Both localStorage and sessionStorage are part of the Web Storage API and provide a way
to store key-value pairs in a web browser. However, they have several key differences:
Scope Data persists across browser Data is available only for the duration of
sessions. the page session.
Storage Data remains until explicitly deleted Data is deleted when the page session
Duration or the browser's storage is cleared. ends (e.g., when the tab or browser is
closed).
Storage Limit Typically around 5-10 MB, depending Typically the same limit as localStorage .
on the browser.
Accessibility Data can be accessed across Data is limited to the tab or window in
different tabs and windows from the which it was created.
same origin.
Use Cases Suitable for storing user preferences, Useful for storing temporary data that is
themes, or data that needs to needed only during a session, such as
persist. form data.
Example:
javascript
// Using localStorage
localStorage.setItem('username', 'JohnDoe');
console.log(localStorage.getItem('username')); // Output: JohnDoe
// Using sessionStorage
sessionStorage.setItem('sessionId', '12345');
console.log(sessionStorage.getItem('sessionId')); // Output: 12345
In this example, localStorage persists even after closing the browser, while
sessionStorage is cleared when the tab or browser is closed.
Key Concepts:
2. Preflight Requests: For certain types of requests (like those with custom headers or
methods other than GET or POST), the browser sends a "preflight" OPTIONS request to
the server to check if the actual request is safe to send.
Example:
Suppose you have a server configured to accept requests from a specific origin:
http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
javascript
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Fetch error:', error));
If the server allows your origin, the request will succeed. Otherwise, the browser will block
the request and log an error.
1. A thunk is typically a function that takes no arguments and returns another function.
The returned function can be called later to execute the deferred computation.
2. Thunks can be useful for controlling when and how operations are executed, allowing
for delayed execution, especially in scenarios involving asynchronous code.
Example:
javascript
function thunkExample() {
const value = 42; // Value to be deferred
return function() {
return value; // Return the deferred value when called
};
}
2. Load Language Resources: Store translations in separate files or objects for each
language.
Example:
json
// en.json
{
"greeting": "Hello, World!"
}
// es.json
{
"greeting": "¡Hola, Mundo!"
}
3. Initialize the i18n Library: Set up the library and load the translations based on the
user's preferred language.
javascript
i18next.init({
lng: 'en', // Default language
resources: {
en: {
translation: {
greeting: "Hello, World!"
}
},
es: {
translation: {
greeting: "¡Hola, Mundo!"
}
}
4. Use Translations in Your Application: Retrieve and display translated strings in your
application.
Example:
javascript
5. Switch Languages: Provide a mechanism for users to change languages, which updates
the current language in the i18n library.
Example:
javascript
function changeLanguage(lang) {
i18next.changeLanguage(lang);
console.log(i18next.t('greeting')); // Output changes based on selected
language
}
By following these steps, you can implement i18n in your JavaScript application, allowing it to
support multiple languages and enhance the user experience for a diverse audience.
Feel free to reach out if you need any further explanations or additional topics!