JavaScript, created by Brendan Eich in 1995, is one of the most widely used web development languages. Here are the common JavaScript Interview Questions asked during interview.
JavaScript Interview Questions for 5 years experience
1. Explain the difference between arrow functions and regular functions in terms of their handling of this
context.
Answer: Arrow functions do not have their own this
context; instead, they inherit this
from the surrounding lexical scope where they are defined. Regular functions, on the other hand, have a dynamic this
context determined by how they are called.
// Regular function
const regularObj = {
name: 'Regular Object',
regularFunction: function() {
console.log('Inside regularFunction:', this.name);
}
};
// Arrow function
const arrowObj = {
name: 'Arrow Object',
arrowFunction: () => {
console.log('Inside arrowFunction:', this.name);
}
};
regularObj.regularFunction(); // Output: "Inside regularFunction: Regular Object"
// In this case, inside arrowFunction, 'this' refers to the global scope
// Because arrow functions inherit 'this' from the surrounding lexical scope
arrowObj.arrowFunction(); // Output: "Inside arrowFunction: undefined"
In this example:
regularObj
has a methodregularFunction
that is a regular function. WhenregularFunction
is called usingregularObj.regularFunction()
,this
inside the function refers to theregularObj
object. Therefore, it correctly logs"Inside regularFunction: Regular Object"
.arrowObj
has a methodarrowFunction
that is an arrow function. WhenarrowFunction
is called usingarrowObj.arrowFunction()
,this
inside the function refers to the global scope because arrow functions inheritthis
from the surrounding lexical scope. Therefore, it logs"Inside arrowFunction: undefined"
becausethis.name
refers to thename
property of the global object, which isundefined
in this context.
2. How do arrow functions behave differently from regular functions when accessing variables in their lexical scope?
Answer: Arrow functions capture the value of variables from their surrounding lexical scope at the time of their definition. In contrast, regular functions resolve variables dynamically based on their invocation context.
// Using a regular function
function regularFunction() {
const localVar = 'I am a local variable';
const innerFunction = function() {
console.log(localVar);
};
return innerFunction;
}
const regularFunc = regularFunction();
regularFunc(); // Output: "I am a local variable"
// Using an arrow function
function arrowFunction() {
const localVar = 'I am a local variable';
const innerFunction = () => {
console.log(localVar);
};
return innerFunction;
}
const arrowFunc = arrowFunction();
arrowFunc(); // Output: "I am a local variable"
In this example:
- In the
regularFunction
,innerFunction
is a regular function. WheninnerFunction
is executed (regularFunc()
), it correctly logs the value oflocalVar
, which is'I am a local variable'
. This is because regular functions resolve variables dynamically based on their invocation context. - In the
arrowFunction
,innerFunction
is an arrow function. WheninnerFunction
is executed (arrowFunc()
), it also correctly logs the value oflocalVar
, which is'I am a local variable'
. This is because arrow functions capture the value of variables from their surrounding lexical scope at the time of their definition. So, even thoughinnerFunction
is executed outside of the scope ofarrowFunction
, it still retains access tolocalVar
.
3. Describe the role of arrow functions in maintaining context in JavaScript.
Answer: Arrow functions are useful for maintaining context by capturing this
from their surrounding lexical scope. This behavior helps in scenarios where you want to preserve the context of this
from the enclosing code.
// Using a regular function
const obj = {
name: 'Regular Object',
regularFunction: function() {
setTimeout(function() {
console.log('Inside regularFunction:', this.name);
}, 1000);
}
};
obj.regularFunction(); // Output: "Inside regularFunction: undefined" (or an error)
// Using an arrow function
const arrowObj = {
name: 'Arrow Object',
arrowFunction: function() {
setTimeout(() => {
console.log('Inside arrowFunction:', this.name);
}, 1000);
}
};
arrowObj.arrowFunction(); // Output: "Inside arrowFunction: Arrow Object"
In this example:
obj
has a methodregularFunction
that is a regular function. InsideregularFunction
, asetTimeout
function is used to logthis.name
after a delay. However, inside the callback function passed tosetTimeout
,this
does not refer toobj
anymore due to the function context being changed bysetTimeout
. As a result,this.name
isundefined
, or it may throw an error if strict mode is enabled.arrowObj
has a methodarrowFunction
that is a regular function. InsidearrowFunction
, asetTimeout
function is used with an arrow function as the callback. Since arrow functions capturethis
from their surrounding lexical scope (arrowFunction
in this case),this.name
inside the arrow function still refers to thename
property ofarrowObj
. Therefore, it correctly logs"Inside arrowFunction: Arrow Object"
after the delay.
4. Explain how arrow functions contribute to avoiding context-related bugs in nested functions.
Answer: Arrow functions alleviate context-related bugs in nested functions by inheriting this
from their enclosing lexical scope. This eliminates the need for workarounds such as storing this
in a variable or using .bind()
to maintain context.
Arrow functions contribute to avoiding context-related bugs in nested functions by capturing the value of this
from their surrounding lexical scope. This behavior ensures that this
inside the arrow function always refers to the same this
value as the outer function, preventing unexpected changes in context.
Here’s an example illustrating how arrow functions help avoid context-related bugs in nested functions:
// Using regular functions
const obj = {
name: 'Regular Object',
regularFunction: function() {
console.log('Inside regularFunction:', this.name);
// Nested regular function
setTimeout(function() {
console.log('Inside nested regular function:', this.name); // 'this' refers to global object or 'undefined' (in strict mode)
}, 1000);
}
};
obj.regularFunction(); // Output: "Inside regularFunction: Regular Object"
// Using arrow functions
const arrowObj = {
name: 'Arrow Object',
arrowFunction: function() {
console.log('Inside arrowFunction:', this.name);
// Nested arrow function
setTimeout(() => {
console.log('Inside nested arrow function:', this.name); // 'this' refers to 'arrowObj'
}, 1000);
}
};
arrowObj.arrowFunction(); // Output: "Inside arrowFunction: Arrow Object"
In this example:
- In the
obj
object, theregularFunction
method uses a nested regular function inside asetTimeout
. Inside the nested regular function,this
does not refer toobj
anymore due to the function context being changed bysetTimeout
. This can lead to bugs or unexpected behavior, such asthis.name
beingundefined
or referring to the global object. - In contrast, in the
arrowObj
object, thearrowFunction
method uses a nested arrow function inside asetTimeout
. Since arrow functions capturethis
from their surrounding lexical scope (arrowFunction
in this case),this.name
inside the arrow function still refers to thename
property ofarrowObj
. This ensures that the context remains consistent and avoids context-related bugs.
5. In what scenarios would you prefer using arrow functions over regular functions?
Answer: Arrow functions are preferred in scenarios where you want to maintain the lexical scope of this
, such as in callbacks, event handlers, or when defining methods in objects. Additionally, arrow functions provide a more concise syntax, making the code cleaner and more readable.
Callbacks: Arrow functions are often preferred for callbacks because they capture the surrounding lexical scope, which can be useful for maintaining the context of this
.
const numbers = [1, 2, 3, 4, 5];
// Using a regular function for Array.prototype.map
const squares = numbers.map(function(num) {
return num * num;
});
console.log(squares); // Output: [1, 4, 9, 16, 25]
// Using an arrow function for Array.prototype.map
const cubes = numbers.map(num => num * num * num);
console.log(cubes); // Output: [1, 8, 27, 64, 125]
Event Handlers: Arrow functions can be useful for event handlers because they maintain the lexical scope, making it easier to access properties of the surrounding object.
<button id="myButton">Click me</button>
<script>
const obj = {
name: 'Arrow Object',
handleClick: function() {
console.log('Button clicked in', this.name);
}
};
document.getElementById('myButton').addEventListener('click', obj.handleClick); // Output: "Button clicked in undefined"
// Using an arrow function to maintain context
document.getElementById('myButton').addEventListener('click', () => obj.handleClick()); // Output: "Button clicked in Arrow Object"
</script>
Defining Object Methods: Arrow functions can be concise when defining methods in objects and they maintain the lexical scope, making them suitable for accessing object properties.
const obj = {
name: 'Arrow Object',
// Using a regular function
regularMethod: function() {
console.log('Regular method called in', this.name);
},
// Using an arrow function
arrowMethod: () => {
console.log('Arrow method called in', this.name);
}
};
obj.regularMethod(); // Output: "Regular method called in Arrow Object"
obj.arrowMethod(); // Output: "Arrow method called in undefined"
In these examples, arrow functions are preferred because they provide a concise syntax and maintain the lexical scope, which is beneficial for accessing this
context and surrounding variables.
6. Explain what lexical scope is in JavaScript and provide an example of how it can be beneficial in a live project.
Lexical scope in JavaScript refers to how the scope of a variable is determined by its position in the code. It means that variables are accessible within the block they are defined in, as well as any nested blocks. This concept allows for predictable variable resolution and helps in organizing and encapsulating code.
Example
Consider a scenario where you’re building a web application with multiple modules. Each module may have its own set of variables and functions. Lexical scope helps in keeping these variables and functions scoped within their respective modules, preventing them from interfering with variables and functions in other modules.
// Module 1
function module1Function() {
const module1Variable = 'Module 1 variable';
console.log(module1Variable);
}
// Module 2
function module2Function() {
const module2Variable = 'Module 2 variable';
console.log(module2Variable);
}
// In the main script
module1Function(); // Output: "Module 1 variable"
module2Function(); // Output: "Module 2 variable"
// console.log(module1Variable); // This would result in an error since module1Variable is not defined in this scope
In this example, module1Variable
and module2Variable
are scoped within their respective modules due to lexical scope. This helps in keeping the variables encapsulated and prevents potential conflicts or unintended access from other parts of the codebase.
7. How can you create objects in JavaScript, and could you provide examples for each method?
In JavaScript, there are several ways to create objects. Here are some of the common ways along with examples:
Object Literal:
You can create an object using object literal notation, which is the simplest way to create an object.
const obj = {
key1: 'value1',
key2: 'value2'
};
Constructor Function:
You can define a constructor function and create objects using the new
keyword.
function Person(name, age) {
this.name = name;
this.age = age;
}
const person1 = new Person('Harry', 30);
Object.create():
You can create objects using Object.create()
method, which allows you to specify the prototype of the newly-created object.
const personPrototype = {
greeting: function() {
return 'Hello, my name is ' + this.name;
}
};
const person2 = Object.create(personPrototype);
person2.name = 'Harry';
person2.age = 25;
Class Syntax (ES6):
With ES6, you can use class syntax to define and create objects.
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
}
}
const car1 = new Car('Toyota', 'Corolla');
Factory Function:
You can create objects using factory functions, which are functions that return objects.
function createPerson(name, age) {
return {
name: name,
age: age,
greeting: function() {
return 'Hello, my name is ' + this.name;
}
};
}
const person3 = createPerson('Harry', 28);
console.log(person3.name); // Output: Harry
console.log(person3.age); // Output: 28
console.log(person3.greeting()); // Output: Hello, my name is Harry
8. Can you explain the usage of the super
keyword in JavaScript with an example?
In JavaScript, the super
keyword is used to call functions or access properties on an object’s parent. It is commonly used in classes when working with inheritance to refer to the superclass’s methods or properties.
Here’s how super
works with examples:
- Calling Superclass Constructor: In a subclass constructor, you can use
super()
to call the constructor of its superclass.
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call the constructor of the superclass
this.breed = breed;
}
}
const myDog = new Dog('Buddy', 'Golden Retriever');
console.log(myDog.name); // Output: Buddy
console.log(myDog.breed); // Output: Golden Retriever
2. Calling Superclass Methods: Inside methods of a subclass, you can use super.method()
to call a method from its superclass with the same name.
class Animal {
speak() {
return 'Animal speaks';
}
}
class Dog extends Animal {
speak() {
return super.speak() + ', but Dog barks'; // Call superclass method and add extra behavior
}
}
const myDog = new Dog();
console.log(myDog.speak()); // Output: "Animal speaks, but Dog barks"
3. Accessing Superclass Properties: In a subclass, you can use super.property
to access properties of its superclass.
class Animal {
constructor(name) {
this.name = name;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call the constructor of the superclass
this.breed = breed;
}
getDescription() {
return super.name + ' is a ' + this.breed;
}
}
const myDog = new Dog('Buddy', 'Golden Retriever');
console.log(myDog.getDescription()); // Output: "Buddy is a Golden Retriever"